DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for πŸ“— What is Go?
Krypton
Krypton

Posted on • Updated on

πŸ“— What is Go?

Original post

When I first read about Go and what it offers I decided to start and learn it. After some time I realized it has some amazing potential, and I wanted to share it here with you. Note that this is just a short blog post to show and explain what Go is and how it works, it's not supposed to be a Wikipedia article πŸ˜‰

πŸ“œ About Go

Go is a statically typed and compiled programming language. Go is designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Syntactically Go is very similar to C, but it has memory safety, garbage collection, structural typing and many other advantages. It's an easy language for developers to learn quickly.

πŸ€” Why has Go been created?

Go was intended as a language for writing server programs that would be easy to maintain over time. It's now being used for writing light-weight microservices and is being used for generating APIs that will later on interact with the front-end. So in short words I could say that it was mainly created for APIs, web servers, frameworks for web applications, etc.

πŸŽ–οΈ Why is Go "so good"?

Its easy concurrency is really easy to make and include in your projects. As you may know, network applications are really dependent of concurrency, and that's why Go's networking features makes it even easier and better.

When declaring interfaces in Go, you don't need to add like in other language the keyword implements or anything similar. Which means we can create an interface and simply make another object implement it, we simply need to make sure that all the methods from the interface are in the object implementing it, just like any other language.

When looking at Go's standard library we can see that we don't always specifically need to get a third-party library. It goes from parsing flags when executing your program to testing.

Since Go get compiled into machine code, we need a compiler. For Go, the compilation time is quite fast compared to some other languages, one reason for that is that Go does not allow unused imports. If you want a comparison to another language, Go is significantly faster than C++ at compilation time, however it also results in Go's binary to be bigger in terms of size.

And as always, who does not like programming languages that are cross-platform? Well, Go got you covered for that, from Android to Linux to Windows, just run go tool dist list in your terminal to see it.

🀯 How hard is Go?

This is a question you often get asked when you learn a new programming language and other people you know may want to learn it. For Go, it's a simple answer because the language itself is simple. Go's syntax is quite small compared to many other languages and therefore easier to remember. Most of the things can be remembered quite easily and this means you won't need to spend a lot of time at looking things up. You can start taking a look at Go here.

πŸ‘‹ A "Hello world!" in Go

A hello world is often included when you explain what a language is, so here it is:

package main

import "fmt"

func main() {
    fmt.Println("Hello world!")
}
Enter fullscreen mode Exit fullscreen mode

πŸ“š What are packages?

Packages are a way to organize your code and to make it reusable. You can import packages from the standard library or from third party libraries. To import a package you can use the following syntax:

import "fmt"
Enter fullscreen mode Exit fullscreen mode

This will import the package fmt from the standard library. You can also import multiple packages at once:

import (
    "fmt"
    "math"
)
Enter fullscreen mode Exit fullscreen mode

You can also import packages with a different name:

import (
    "fmt"
    m "math"
)
Enter fullscreen mode Exit fullscreen mode

This will import the package math but instead of using math you can use m to call the functions from the package.

Installing packages

To install a package you can use the following command:

go get <link>
Enter fullscreen mode Exit fullscreen mode

The <link> is the link to the package you want to install. For example, if you want to install the https://github.com/kkrypt0nn/spaceflake package you can use the following command:

go get github.com/kkrypt0nn/spaceflake
Enter fullscreen mode Exit fullscreen mode

Usually the way to download the package is written in the README.md file.

🌐 Did I hear web server?

Correct! Go is widely used for web servers and/or APIs. This can go from a very basic REST API to using Websockets. In comparison to other languages, Go does not need some overcomplicated web framework, Go's standard library already has this implemented and ready for us. Of course there are third party libraries that implements some additions.

To create a very basic web server we need to import two default libraries like the following:

import (
    "fmt" // This is to give output when requesting a page (also used in the hello world example)
    "net/http" // This is needed to handle requests, etc.
)
Enter fullscreen mode Exit fullscreen mode

Now we can create a simple handler that will listen to the port 1337 and register a new /hello route.

func helloWorld(response http.ResponseWriter, request *http.Request) {
    fmt.Fprintf(response, "Hello from the Gopher side!")
}

func main() {
    http.HandleFunc("/hello", helloWorld)
    http.ListenAndServe(":1337", nil)
}
Enter fullscreen mode Exit fullscreen mode

Now run the code with go run main.go and go to 127.0.0.1:1337/hello.

As you can see the web server works like a charm!

In around 3-7 lines of code we managed to start a web server and create a route that will display some text.

Now it's up to you to make your creative projects :)

πŸ₯ˆ What is Go's concurrency?

Goroutines

Python developers may know what coroutines are, however to include them in your project you need an additional library called asyncio. Yes that's not a big deal as most of the Python features relies on libraries anyways.

The difference with Go, is that you can easily create a so called Goroutine to make functions run concurrently. Here's a very easy example on how to create a Goroutine:

package main

import (
    "fmt"
    "time"
)

func echo(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go echo("world")
    echo("Hello")
}
Enter fullscreen mode Exit fullscreen mode

As you may notice, I just had to add the keyword go before calling the function to turn the function into a Goroutine. And of course I can call the echo() function without having to mark it as a Goroutine. After running the code, here is a sample output I got:

Hello
world
world
Hello
world
Hello
Hello
world
world
Hello
Enter fullscreen mode Exit fullscreen mode

You can clearly see, that the echo("Hello") method runs without having to wait for the Goroutine to end, this is concurrency.

Channels

What makes Go's concurrency different and unique from different languages are so called channels. You can see channels as being pipes that transfer data. This is used to send values and data from one goroutine to another one, or just to get data back from a goroutine.
Take this example:

package main

import "fmt"

func sum(list []int, channel chan int) {
    sum := 0
    for _, value := range list {
        sum += value
    }
    channel <- sum // Here we send the sum to the channel
}

func main() {
    list1 := []int{1, 2, 3}
    list2 := []int{9, 8, 7}

    channel := make(chan int) // Here we create a channel that accepts integers
    go sum(list1, channel)
    go sum(list2, channel)
    x := <-channel // Here we receive data from the channel
    y := <-channel

    fmt.Println(x, y, x+y) // Output: 24 6 30
}
Enter fullscreen mode Exit fullscreen mode

This small example simply sums the numbers from a slice and puts the work between two goroutines so that they are being calculated concurrently and is therefore faster. Once both goroutines gave their output, it will calculate the final result and print it in the console. The arrows such as <- simply describe from where to where the data goes, so either from the channel to the variable or from the variable in the channel.

🌳 Does Go have objects?

Of course! There's just one slight difference here. Go doesn't directly have a type object, but they have a type that matches the definition of a data structure that integrates both code and behavior. It's called a struct. Let me show you a very simple example.

Structs

package main

import "fmt"

type Rectangle struct {
    Width  int
    Height int
}

// The '(rect *Rectangle)' before the method name shows to which object the method will operate, in this case the 'rectangle' object.
func (rect *Rectangle) Area() int {
    return rect.Width * rect.Height
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    // As expected, the output is 50.
    fmt.Println("Area: ", rect.Area())
}
Enter fullscreen mode Exit fullscreen mode

Implicit inheritance

package main

import "fmt"

type Shape interface {
    Area() float64
}

type Rectangle struct {
    Width  float64
    Height float64
}

func (rect Rectangle) Area() float64 {
    return rect.Width * rect.Height
}

func main() {
    var s Shape = Rectangle{Width: 10, Height: 50}
    fmt.Println(s.Area())
}
Enter fullscreen mode Exit fullscreen mode

As you can see from the example above, the struct Rectangle implements the interface Shape but nowhere in the code it's clearly written, that it implements it.

πŸ”ͺΒ Hello Jason!

Jason? Yes, JSON. JSON is widely used in web development and is a very easy way to send data between a client and a server. Go has a built-in library to handle JSON, so let's see how to use it.

Unmarshalling

Unmarshalling is the process of converting a JSON string into a Go object. So let's suppose we have an API that returns content like the following:

{
    "name": "Gopher",
    "age": 5,
    "hobbies": [
        "coding",
        "eating",
        "sleeping"
    ]
}
Enter fullscreen mode Exit fullscreen mode

We can easily parse this JSON into a Go struct like the following:

package main

import (
    "encoding/json"
    "fmt"
)

// Gopher represents a gopher structure that will be parsed from JSON.
type Gopher struct {
    Name    string   `json:"name"` // The 'json:"name"' is used to tell the JSON parser to use the 'name' field from the JSON.
    Age     int      `json:"age"`
    Hobbies []string `json:"hobbies"`
}

func main() {
    jsonStr := `
    {
        "name": "Goophy",
        "age": 5,
        "hobbies": [
            "coding",
            "eating",
            "sleeping"
        ]
    }
    `
    // This variable will hold the parsed JSON data.
    var gopher Gopher
    // Here we parse the JSON string into the 'gopher' variable.
    json.Unmarshal([]byte(jsonStr), &gopher)

    fmt.Println(gopher) // {Goophy 5 [coding eating sleeping]}
    fmt.Println("Name:", gopher.Name) // Name: Goophy
    fmt.Println("Age:", gopher.Age) // Age: 5
    for _, h := range gopher.Hobbies {
        fmt.Println("Hobby:", h) // Hobby: coding/eating/sleeping
    }
}
Enter fullscreen mode Exit fullscreen mode

We can pretty much handle any JSON data with this method, as long as the JSON data matches the struct.

Marshalling

But what if your application doesn't need to interact with an external API because it is an API, you most likely want to convert your Go struct into JSON. This is called marshalling, and this is where the json.Marshal() method comes in handy. Let's see how to use it:

package main

import (
    "encoding/json"
    "fmt"
)

type Gopher struct {
    Name              string   `json:"name"` // The 'json:"name"' is used to tell the JSON marshaller to specifically use for the field name 'name' and not something random.
    Age               int      `json:"age"`
    Hobbies           []string `json:"hobbies"`
    SecretInformation string   `json:"-"` // The '-' tells the JSON marshaller to ignore this field, we can still use it within our code. Alternatively, we can simply lowercase the field name, which will also tell the JSON marshaller to ignore it, though the field will not be exported.
}

func main() {
    // Here we create our struct.
    gopher := Gopher{
        Name:              "Goophy",
        Age:               5,
        Hobbies:           []string{"coding", "eating", "sleeping"},
        SecretInformation: "I am a secret agent for the Gopher Nation.",
    }
    // Here we convert the 'gopher' struct into JSON.
    json, _ := json.Marshal(gopher)
    fmt.Println(string(json)) // {"name":"Goophy","age":5,"hobbies":["coding","eating","sleeping"]}
}
Enter fullscreen mode Exit fullscreen mode

Now that we have the JSON, we can repond to the API request with it.

🧬 Generics

As of Go 1.18 we now have generics support available, which is a great addition to the language. It's not as powerful as other languages, but it's a great start.

Here is some code that works perfectly fine without generics.

package main

import "fmt"

func getSumIntList(list []int) int {
    sum := 0
    for _, value := range list {
        sum += value
    }
    return sum
}

func getSumFloatList(list []float64) float64 {
    sum := 0.0
    for _, value := range list {
        sum += value
    }
    return sum
}

func main() {
    myListOfInts := []int{1, 2, 3, 4, 5}
    intSum := getSumIntList(myListOfInts)
    fmt.Println(intSum) // 15

    myListOfFloats := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
    floatSum := getSumFloatList(myListOfFloats)
    fmt.Println(floatSum) // 16.5
}
Enter fullscreen mode Exit fullscreen mode

Now this is all not so wonderful because we pretty much copy paste the exact same logic, just for two different data types. This is where generics come in handy. Let's rewrite the code to use generics.

package main

import "fmt"

func getSumList[T int | float64](list []T) T {
    var sum T
    for _, value := range list {
        sum += value
    }
    return sum
}

func main() {
    myListOfInts := []int{1, 2, 3, 4, 5}
    intSum := getSumList(myListOfInts)
    fmt.Println(intSum) // 15

    myListOfFloats := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
    floatSum := getSumList(myListOfFloats)
    fmt.Println(floatSum) // 16.5
}
Enter fullscreen mode Exit fullscreen mode

We got the exact same output!
To explain that very easily, we first kind of define a new type T which can be either an int or a float64 - this is what [T int | float64] does. We can of course go on and say we want to allow the type any but that isn't ideal when using += like in the example and won't work as well.
The second part is where we say what type we want to have in our parameter - (list []T) - this will make sure we provide a list that contains elements of the type T which is, either int or float64.
Finally, we have the return value of the function to be of type T as well.

That way we can reuse our function for different data types and don't have to write the same logic over and over again.

πŸ‘† Pointers

Similarly to C, Go has pointers as well. They are used to point to a memory address of a variable. This is useful when you want to change the value of a variable inside a function, but you don't want to return the value. You can simply pass the pointer to the variable and change the value inside the function. Let's take a look at an example without using pointers:

package main

import "fmt"

func changeValue(value int) {
    value = 10
}

func main() {
    x := 5
    changeValue(x)
    fmt.Println(x) // 5
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it will still print 5 and not 10, some people might expect it to print 10 though. The reason for that is because we didn't change the value of x but instead we changed the value of the variable value inside the function. To change the value of x we need to use pointers.

package main

import "fmt"

func changeValue(value *int) {
    *value = 10
}

func main() {
    x := 5
    changeValue(&x)
    fmt.Println(&x) // 0x14000014090
    fmt.Println(x) // 10
}
Enter fullscreen mode Exit fullscreen mode

To understand it a bit better, I've added a print of &x. This will print the memory address of x which is 0x14000014090. The * in front of value is called a dereference operator and it will get the value of the variable that the pointer points to. So in this case, it will get the value of x and change it to 10. The & in front of x is called a reference operator and it will get the memory address of the variable x. This is what we pass to the function so that we can change the value of x inside the function, as it points to the memory.

We can also use pointers with custom structures as well.

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func changePersonAge(person *Person) {
    person.Age = 10
}

func main() {
    person := Person{
        Name: "John",
        Age: 20,
    }
    changePersonAge(&person)
    fmt.Println(person.Age) // 10
}
Enter fullscreen mode Exit fullscreen mode

The entire pointer story might be a bit confusing at first, hence I recommend to play around and see how it works.

😎 Popular open-source projects

As you might have expected, there are some really popular projects that are using Go. Here is just a small list among a lot of other projects:

🏫 How do I get started?

Be part of the Gophers :D

I'm glad you're interested in learning more about Go by yourself!

Installation

The installation is really easy regardless of the operating system you have, simply go to the download page and follow the instructions. Oh and remember, Go works on every operating system!

Learning resources

I don't really have specific learning resources but these are the things I've used to start to learn and code in Go:

I'd be happy to see you become part of the Gophers!

Top comments (0)

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›