Edwin Dalorzo
Edwin Dalorzo

Reputation: 78589

How does Go bind methods to Objects?

I just started learning Go a few days ago.Today, we were debugging a piece of code for a while when we found something that seem counterintuitive of Go to do.

First we defined an interface and a data structure that implements it.

type Executer interface {
    Execute()
}

type whatever struct {
    name string
}

func (this *whatever) Execute() {
    log.Println(this.name)
}

Now consider that I have a nil pointer to whatever and I try to call the method Execute. In other object-oriented languages I have used so far, this would call a null pointer error at the point of calling the method (i.e. w.Execute()) since the object pointer is null. Interestingly, in Go, the method is invoked, the null pointer error occurs at the Execute method when I try to dereference this.name. Why not at the point of calling the method?

func main() {
    var w *whatever
    w.Execute()
}

So, what I'm seeking to understand now is how is this possible? Does this mean that Go only does early method binding at compile time and at runtime there is no binding of the method with a specific object?

Upvotes: 1

Views: 3526

Answers (1)

icza
icza

Reputation: 417672

The receiver is just an "ordinary" argument to the function. Ordinary parameters may be of pointer types. When then are, you are allowed to pass nil as the argument, which is perfectly valid. All you need to keep in mind is not to dereference nil pointer arguments. The same applies to the special receiver parameter too. If it's a pointer, it may be nil, you just must not dereference it.

Spec: Method declarations:

The receiver is specified via an extra parameter section preceding the method name.

... The method is said to be bound to its receiver base type and the method name is visible only within selectors for type T or *T.

Allowing nil receiver values is not just something not forbidden, it has practical uses. For an example, see Test for nil values in nested stucts.

In Java you can call static methods on null objects too. It's true you can't do the same in Go, because Go does not have modifiers like static, public, private etc. In Go there are only exported and non-exported methods (implied by the first latter of their name).

But Go offers something similar too: Method expressions and Method values. If you have a method m with T receiver type, the expression T.m will be a function value whose signature contains the parameters and result types of m "prefixed" with the receiver type.

For example:

type Foo int

func (f Foo) Bar(s string) string { return fmt.Sprint(s, f) }

func main() {
    fooBar := Foo.Bar // A function of type: func(Foo, string) string
    res := fooBar(1, "Go")
    fmt.Println(res)
}

Foo.Bar will be a function with type func (Foo, string) string, and you can call it like any other ordinary function; and you also have to pass the receiver as the first argument. The above app outputs (try it on the Go Playground):

Go1

Going a little "forward", we are not required to store Foo.Bar in a variable, we can directly call Foo.Bar:

fmt.Println(Foo.Bar(1, "Go"))

Which outputs the same (try it on the Go Playground). Now this almost looks like a static method call in Java.

And as the "final" step, when we use the above expression on a value of Foo itself (instead of the type identifier), we arrive at the method values, which saves the receiver and so the type of a method value does not include the receiver, its signature will be that of the method, and we can call it without having to pass a receiver:

var f Foo = Foo(1)
bar := f.Bar
fmt.Println(bar("Go"))

This again will output the same, try it on the Go Playground.

See related questions:

Pass method argument to function

golang function alias on method receiver

Upvotes: 5

Related Questions