In this series, we will be discussing interesting aspects and corner cases of Golang. Some questions will be obvious, and some will require a closer look even from an experienced Go developer. These question will help to deeper the understanding of the programming language, and its underlying philosophy. Without much ado, let's start with the first part.


Value assignment


What value y will have at the end of the execution?


func main() {
    var y int
    for y, z := 1, 1; y < 10; y++ {
        _ = y
        _ = z
    }
    fmt.Println(y)
}

According to the specification, for loop creates its own scope. Therefore, we are dealing with two different scopes there: one inside the main function, and one inside the for loop. Therefore, we don't reassign y inside the for loop initialization, but instead creating new y that shadows the one from the outer scope. Therefore, the outer y is not affected, and the program will output 0.


A part of a string


In this example, we have a string and would like to access a part of it. What would be the result of the following snippet?


s := "9"

v1 := s[0]

for _, v2 := range s {
    fmt.Println(v1)
    fmt.Println(v2)
    fmt.Println(v1 == v2)
    break // a single loop iteration
}

The first two print statement would output the same result. A string in Golang is an immutable array of bytes and every character is encoded in UTF-8. In this case, we are dealing with the ASCII-only string, therefore, character 9 will be encoded as a single byte with the value equal to 57. Therefore, the first print statement would output 57. Exactly the same value would be printed at the second line, as in this case, we will have rune r that consists of a single byte.


However, the program won't compile due to the third line, as we are dealing with different types: uint8 (under alias byte) and int32 (under alias rune). The numeric value of the variables is equal, but their types are different, therefore, they cannot be compared without the explicit type conversion.


Struct Conversion


In this example, we have two similar structs that differ only in struct tags. Such an approach could be used in a real life. For example, you can have a separate representation of a single domain model in different packages: package db that is responsible for database persistence and package api that is responsible for handling the incoming requests. In this case, the structs would be equal save for the struct tags. What would be the result of the following code snippet? #v outputs the full Golang representation of the value, including the type of the struct and its field names.


type Struct1 struct {
    A int `db:"a"`
}

type Struct2 struct {
    A int `json:"a"`
}

func main() {
    s1 := Struct1{}
    s2 := Struct2(s1)
    fmt.Printf("%#v", s2)
}

That's a tricky question because according to the Golang specification a struct tag is a part of the struct definition. Therefore, at some point, it wasn't possible to do the conversion. However, later the Go team decided to relax the constraint (without changing the definition of the struct in the spec), and now such conversion is permitted.


main.Struct2{A:1}


How about this snippet? We are trying to convert Struct1 to Struct2. All information necessary for Struct2 is available in Struct1. However, there is also a redundant field B in Struct1.


type Struct1 struct {
    A int
    B int
}

type Struct2 struct {
    A int
}

func main() {
    s1 := Struct1{}
    s2 := Struct2(s1)
    fmt.Printf("%#v", s2)
}

In this case, the specification does not care whether we have all the information to instantiate Struct2 from Struct1. Struct1 has an extra field, and that's the end of the deal: the operation is not permitted, and the code won't compile.


JSON Unmarshalling


Will the existing records in the map be preserved when we unmarshal JSON-encoded values into it? What happens in the case of a collision (note key Field1) ?


s := map[string]int{
        "Field1": 1,
        "Field2": 2,
}

data := `{"Field2": 202}`

err := json.Unmarshal([]byte(data), &s)
if err != nil {
    panic(err)
}
fmt.Println(s)

Existing records in the map will be preserved. In the case of a collision, the value will be overwritten.


map[Field1:1 Field2:202]


What about structs?


type request struct {
    Field1, Field2 int
}
r := request{Field1: 1, Field2: 2}

data := `{"Field2": 202}`

err := json.Unmarshal([]byte(data), &r)
if err != nil {
    panic(err)
}
fmt.Println(r)

The same logic is valid here:


{Field1:1 Field2:202}


And that all the question for today :) How many right answers did you get out of four?