Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

golang; trouble understanding a function as a receiver

Tags:

go

I'm trying to read through this: https://blog.golang.org/error-handling-and-go specifically the section titled Simplifying repetitive error handling.

They call http.Handle like this:

func init() {
    http.Handle("/view", appHandler(viewRecord))
}

http.Handle's second argument expects a type Handler (https://golang.org/pkg/net/http/#Handler) which needs to have a method serveHttp.

The serveHttp function here:

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

So, their type appHandler now implements the Handler interface because it implements ServeHTTP, I got that. So it can be used in the Handle function whereas viewRecord cannot.

Where I'm getting confused is between the relationship between viewRecord which is of type appHandler and ServeHTTP. Which calls which? They make a parenthetical comment about how "a function can be a receiver too" and I think that's where I'm getting tripped up.

Here, with fn appHandler as the receiver, I would expect something like viewRecord.serveHTTP(), but that doesn't make sense, and viewRecord is a function. What I think is happening is that the Handle function calls serveHTTP, but how does serveHTTP call viewRecord?

Also does appHandler(viewRecord) do a cast?

Basically I'm looking for some clarity around what it means for a function to be a receiver. I'm new-ish to go and I think I accidentally landed on a non-trivial landmine here.

like image 905
Tommy Avatar asked Feb 06 '20 20:02

Tommy


People also ask

What is the use of function receiver in Golang?

Function Receiver sets a method on variables that we create. This seems weird at first, but when you get the example, it will be clear as crystal. Let’s create a custom type in Golang and then assign that type to a variable. Now, you can set a method on that variable, and that method is Receiver function.

What is a method in Golang?

Welcome to tutorial no. 17 in Golang tutorial series. A method is just a function with a special receiver type between the func keyword and the method name. The receiver can either be a struct type or non-struct type. The syntax of a method declaration is provided below.

Is *P area () valid in Golang?

This is perfectly valid. The reason is that the line p.area (), for convenience will be interpreted by Go as (*p).area () since area has a value receiver. Similar to value arguments, functions with pointer arguments will accept only pointers whereas methods with pointer receivers will accept both pointer and value receiver.

How do you know if a method has a value receiver?

When a method has a value receiver, it will accept both pointer and value receivers. Let's understand this by means of an example. function func area (r rectangle) in line no.12 accepts a value argument and method func (r rectangle) area () in line no. 16 accepts a value receiver.


2 Answers

Any type can be a receiver. For instance:

type X int

Here, X is a new type, and you can create methods for it:

func (x X) method() {
  // Do something with x
}

In Go, functions are like any other type. So if you have a function type:

type F func()

Here, F is a new type, hence you can define methods for it:

func (x F) method() {
   x()
}

With the above declaration, now you can call value.method() if value is of type F.

a:=F(func() {fmt.Println("hey")})
a.method()

Here, a is a variable of type F. F has a method called method, so you can call a.method. When you call that, a.method calls a, which is a function.

Going back to you example, appHandler appears to be a function type:

type appHandler func(http.ResponseWriter, *http.Request)

So any function with that signature can be used in place of a appHandler. Let's say you write such a function:

func myHandler(http.ResponseWriter, *http.Request) {
  // Handle request
}

You can pass this function whereever an appHandler is requested. However, you cannot pass if to where a Handler is required without writing a struct like this:

type myHandlerStruct struct{}

func (myHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   myHandler(w,r)
}

Instead of definine a new struct, you can define a method for the type appHandler:

func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   a(w,r)
}

Now you can pass appHandler to where an appHandler required as well as where a Handler required. If it is called as a Handler, the ServeHTTP method will simply forward the call to the underlying function.

like image 198
Burak Serdar Avatar answered Nov 15 '22 09:11

Burak Serdar


OK lots of questions there but I'll do my best to cover it all.

This defines a method ServeHTTP on the type appHandler, which means that appHandler is now a valid net/http.Handler. The type appHandler happens to be a function type, so yes - here you have a function value with methods you can call on it, separately from calling the function itself. This is already how http.HandleFunc works in the standard library, by the way - checking its source code might also help understand how this works.

When a handler is registered, net/http calls its ServeHTTP method to handle incoming requests. That method on our appHandler type is:

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

So, there on the first line of the function, ServeHTTP calls the appHandler function - fn appHandler is our receiver, making fn a function value, which we can call:

fn(w, r)

This is utilized by converting our handler function to the appHandler type:

http.Handle("/view", appHandler(viewRecord))

This is no different really than the more common middleware pattern:

func middleware(fn func(w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        if err := fn(w, r); err != nil {
            http.Error(w, err.Error(), 500)
        }
    }
}

This does the exact same thing, but using a closure and function parameter instead of a method and function receiver. This is utilized by calling our wrapper function:

http.Handle("/view", http.HandlerFunc(middleware(viewRecord)))

This uses our middleware function to wrap viewRecord and converts it to a http.HandlerFunc (which, as mentioned earlier, will in fact do the same thing appHandler was doing with the function type method).

like image 25
Adrian Avatar answered Nov 15 '22 09:11

Adrian