by Leon Rosenshein

Pass By Value

Golang is pass by value. There is no pass by reference. It will allocate a new variable to hold every parameter passed to a function. Golang does however have pointers. And you can pass pointers to a function. In practice it works pretty much the same as pass by reference in C++. Inside the called function you have a pointer to a value declared/existing outside the scope of the function and can modify it.

This is a good thing. For slices, maps, and other large objects you don't have to copy so much data. And if you want to change the external value of a parameter you need to be explicit about it. It helps folks feel safer when calling functions.

Golang is also strongly typed. Every variable has a type, and there is no default conversion. You can't assign  float64 to an int and expect it to compile. You need to make an explicit conversion. Again, this is a good thing. You might need to type a little more code, but you're never going to be surprised by an unintended truncation of float to int.

Additionally, as strongly typed as it is, it also has structural typing (almost the same as duck typing, but happens at compile time). It's not inheritance, but it lets you specify the interface you need a function parameter to have and then you can pass in any struct that has supplies that interface. It's a big part of idiomatic Golang and lets you do all sorts of cool composition.

However, there is a bit of a loophole in there. Because of the way that Method Sets are defined, a pointer to T (*T) implements all of the methods that T does. So according to structural typing T and *T implement the same method set. So far, so good.

But it bit me the other day. Say you have a function that is going to unmarshal a json blob to a struct. You have to tell it what the struct is, so you pass in a pointer to the struct and then use reflection to do the unmarshalling. Since you don't want to have a new function for every type you use a bare `interface{}` as the parameter type. Everything works fine.


func MergeEntity(entityStructPtr interface{}, patch interface{}) interface{} {

...

if err := json.Unmarshal(entityBytes, entityStructPtr); err != nil {

    panic(err)

}

...

}


Unless you call the function without a pointer. The compiler can't tell the difference between T and *T in this case because they both implement the bare `interface{}` method set so both

MergeEntity(entity1, entity2)

and

MergeEntity(&entity1, entity2)

compile, but the first one throws a panic from json.Unmarshal because entityStructPtr isn't really a pointer.

And our unit testing didn't cover the combination of options that led to that codepath. So we got a runtime crash. And worse, from the message/trace, it wasn't obvious what the problem was. So, I fixed it by adding the missing & AND I added a bit of guard code to panic with a better error message.

paramType := reflect.ValueOf(entityStructPtr)

if paramType.Kind() != reflect.Ptr {

    panic("lib.MergeEntity: entityStructPtr must be a pointer not a value")

}

So the next person who runs into the problem will at least know what the problem is without having to do deep introspection of the code.