How to return concrete implementation to satisfy interface

118 views
Skip to first unread message

Deividas Petraitis

unread,
Jun 17, 2022, 3:11:28 PM6/17/22
to golang-nuts
Today while working on my side project I have encountered interesting issue and despite I was able to overcome it I have realised that I might be missing some import concepts about Go.
Let me share example code to demostrate the issue I am referring to:

```go
package main

import (
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    // We can pass GorillaRouter as Router, because it implement required methods
    _ = ServerHandler{
        router: NewGorillaRouter(),
    }

    // We can not pass FailingRouter as Router, because of:
    // cannot use FailingRouter{} (value of type FailingRouter) as type Router in struct literal:
    // FailingRouter does not implement Router (wrong type for HandleFunc method)
    //    have HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) *FailingRoute
    //    want HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) Route
    _ = ServerHandler{
        router: FailingRouter{},
    }
}

type ServerHandler struct {
    router Router
}

type Router interface {
    HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) Route
}

type Route interface {
    Methods(methods ...string) Route
}

func NewGorillaRouter() *GorillaRouter {
    return &GorillaRouter{
        router: mux.NewRouter(),
    }
}

type GorillaRouter struct {
    router *mux.Router
}

func (gr *GorillaRouter) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) Route {
    return &GorillaRoute{
        route: gr.router.HandleFunc(path, handler),
    }
}

type GorillaRoute struct {
    route *mux.Route
}

func (r *GorillaRoute) Methods(methods ...string) Route {
    r.route = r.route.Methods(methods...)
    return r
}

type FailingRouter struct{}

func (fr *FailingRouter) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) *FailingRoute {
    return &FailingRoute{}
}

type FailingRoute struct{}

func (r *FailingRoute) Methods(methods ...string) *FailingRoute {
    return r
}
```
 
For convienence you can find soure code from above and run it here: https://go.dev/play/p/M6N48FeegND
Now when code and context is clear question is following: why in `FailingRouter` assignment compiler is not happy while in `GorillaRouter` it is happy?

Or to rephrase, why assigning concrete implementation to a parameter of interface type is accepted:

```go
type ServerHandler struct {
    router Router // assigning GorillaRouter to router is accepted
}
```

but vice versa is not and throws an error:

```go
HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) *FailingRoute // returning concrete that implements Router is not allowed
```
Is my approach  ( `GorillaRouter` ) correct solution for this problem?

P.S. Sorry about the title, I am still not sure is it right for this problem description.

Axel Wagner

unread,
Jun 17, 2022, 3:22:12 PM6/17/22
to Deividas Petraitis, golang-nuts
This is called "covariance" and the FAQ attempts to answer this question:
Personally, I'm not fully convinced Go shouldn't have co/contravariant `func`, but I feel this has been ingrained for over ten years now and it's unlikely to change.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/fdc03ca3-f899-455a-8b7c-319c646f483an%40googlegroups.com.

Axel Wagner

unread,
Jun 20, 2022, 2:44:01 PM6/20/22
to Deividas Petraitis, golang-nuts
[+golang-nuts again. Please don't respond off-list, so anyone can benefit from the discussion]

On Mon, Jun 20, 2022 at 8:32 PM Deividas Petraitis <h...@deividaspetraitis.lt> wrote:
Thank you for reply, it was really helpful! I wish groups would allow to edit the title.
According to he FAQ it seems like there should be some other ways to achieve what I am trying to accomplish here:
> Programmers who want covariant result types are often trying to express a type hierarchy through interfaces. In Go it's more natural to have a clean separation between interface and implementation.
I tried to google based on this keyword and found few interesting articles why covariance return types would be difficult to achieve, but not how to solve this problem using Go constructs.
In your opinion is my solution is right one for this problem? If not could you please give me few more ideas/hints. :)

You have to change the return type from `*FailingRoute` to `mux.Route`, so the types match.

Brian Candler

unread,
Jun 20, 2022, 3:50:59 PM6/20/22
to golang-nuts
If you're new to Go, beware of one thing when returning interface values which are implemented by pointers.  An interface value can be nil (i.e. the zero value of interface: no type and no value); or it can contain a concrete value a concrete pointer type - where the concrete value itself is nil.  These two cases are distinct.

Reply all
Reply to author
Forward
0 new messages