200

I want to add a convenience util method on to gorilla/mux Route and Router types:

package util

import(
    "net/http"
    "github.com/0xor1/gorillaseed/src/server/lib/mux"
)

func (r *mux.Route) Subroute(tpl string, h http.Handler) *mux.Route{
    return r.PathPrefix("/" + tpl).Subrouter().PathPrefix("/").Handler(h)
}

func (r *mux.Router) Subroute(tpl string, h http.Handler) *mux.Route{
    return r.PathPrefix("/" + tpl).Subrouter().PathPrefix("/").Handler(h)
}

but the compiler informs me

Cannot define new methods on non-local type mux.Router

So how would I achieve this? Do I create a new struct type that has an anonymous mux.Route and mux.Router fields? Or something else?

wasmup
  • 14,541
  • 6
  • 42
  • 58
Daniel Robinson
  • 13,806
  • 18
  • 64
  • 112
  • Interestingly extension methods are considered as non object-oriented ([*`“extension methods are not object-oriented”`*](https://blogs.msdn.microsoft.com/ericlippert/2008/10/28/the-future-of-c-part-two/)) for C#, but when looking at them today, I was immediately remembered of Go's interfaces (and its approach to rethink object orientation), and then I had this very question. – Wolf Dec 22 '16 at 11:36

3 Answers3

258

As the compiler mentions, you can't extend existing types in another package. You can define your own type backed by the original as follows:

type MyRouter mux.Router

func (m *MyRouter) F() { ... }

or by embedding the original router:

type MyRouter struct {
    *mux.Router
}

func (m *MyRouter) F() { ... }

...
r := &MyRouter{router}
r.F()
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
jimt
  • 25,324
  • 8
  • 70
  • 60
  • 23
    Or just use a function...? – Paul Hankin Mar 02 '15 at 00:02
  • 10
    @Paul doing this is required to override functions like String() and MarshalJSON() – Riking Feb 04 '16 at 03:40
  • 52
    If you do the first part, then how do you coerce the `mux.Router` instances to `MyRouter`s? e.g. if you have a library that returns `mux.Router` but you want to use your new methods? – docwhat Apr 30 '16 at 03:40
  • how to use the first solution? MyRouter(router) – tfzxyinhao Jun 16 '16 at 07:33
  • 1
    embedding seems a bit more practical to use. – ivanjovanovic Jan 30 '17 at 16:08
  • @PaulHankin how many ways are at disposal for adapting an struct to an interface? as far i get to know, By type embed (or wrap) or type alias there is a way to archive it. – Victor Aug 01 '18 at 23:19
  • @all, above i leave an open question... please anyone with the time and will, consider to provide an opinion! Would be nice! – Victor Aug 16 '18 at 18:11
  • @Victor, there is precisely one way to make a struct satisfy an interface; the type must implement the methods as defined by the interface. – theherk Aug 21 '18 at 22:56
  • I did only the golang official tour to learn the language. So I feel quite empowered after I read this answer and came to know of Embedding! – aniztar Feb 19 '21 at 04:50
202

I wanted to expand on the answer given by @jimt here. That answer is correct and helped me tremendously in sorting this out. However, there are some caveats to both methods (alias, embed) with which I had trouble.

note: I use the terms parent and child, though I'm not sure that is the best for composition. Basically, parent is the type which you want to modify locally. Child is the new type that attempts to implement that modification.

Method 1 - Type Definition

type child parent
// or
type MyThing imported.Thing
  • Provides access to the fields.
  • Does not provide access to the methods.

Method 2 - Embedding (official documentation)

type child struct {
    parent
}
// or with import and pointer
type MyThing struct {
    *imported.Thing
}
  • Provides access to the fields.
  • Provides access to the methods.
  • Requires consideration for initialization.

Summary

  • Using the composition method the embedded parent will not initialize if it is a pointer. The parent must be initialized separately.
  • If the embedded parent is a pointer and is not initialized when the child is initialized, a nil pointer dereference error will occur.
  • Both type definition and embed cases provide access to the fields of the parent.
  • The type definition does not allow access to the parent's methods, but embedding the parent does.

You can see this in the following code.

working example on the playground

package main

import (
    "fmt"
)

type parent struct {
    attr string
}

type childAlias parent

type childObjParent struct {
    parent
}

type childPointerParent struct {
    *parent
}

func (p *parent) parentDo(s string) { fmt.Println(s) }
func (c *childAlias) childAliasDo(s string) { fmt.Println(s) }
func (c *childObjParent) childObjParentDo(s string) { fmt.Println(s) }
func (c *childPointerParent) childPointerParentDo(s string) { fmt.Println(s) }

func main() {
    p := &parent{"pAttr"}
    c1 := &childAlias{"cAliasAttr"}
    c2 := &childObjParent{}
    // When the parent is a pointer it must be initialized.
    // Otherwise, we get a nil pointer error when trying to set the attr.
    c3 := &childPointerParent{}
    c4 := &childPointerParent{&parent{}}

    c2.attr = "cObjParentAttr"
    // c3.attr = "cPointerParentAttr" // NOGO nil pointer dereference
    c4.attr = "cPointerParentAttr"

    // CAN do because we inherit parent's fields
    fmt.Println(p.attr)
    fmt.Println(c1.attr)
    fmt.Println(c2.attr)
    fmt.Println(c4.attr)

    p.parentDo("called parentDo on parent")
    c1.childAliasDo("called childAliasDo on ChildAlias")
    c2.childObjParentDo("called childObjParentDo on ChildObjParent")
    c3.childPointerParentDo("called childPointerParentDo on ChildPointerParent")
    c4.childPointerParentDo("called childPointerParentDo on ChildPointerParent")

    // CANNOT do because we don't inherit parent's methods
    // c1.parentDo("called parentDo on childAlias") // NOGO c1.parentDo undefined

    // CAN do because we inherit the parent's methods
    c2.parentDo("called parentDo on childObjParent")
    c3.parentDo("called parentDo on childPointerParent")
    c4.parentDo("called parentDo on childPointerParent")
}
Inanc Gumus
  • 25,195
  • 9
  • 85
  • 101
theherk
  • 6,954
  • 3
  • 27
  • 52
  • 1
    your post is very helpful as show a lot of research and effort trying to compare point by point each tecniche.. allow me to encourage you to think what happens in terms of conversion to a given interface. I mean, if you have a struct and you want that struct (from a third party vendor let's assume) you want to adapt to a given interface, who do you manage to get that? You could employ type alias or type embed for that. – Victor Aug 02 '18 at 17:15
  • @Victor I don't follow your question, but I think your asking how to get a struct you don't control to satisfy a given interface. Short answer, you don't except by contributing to that codebase. However, using the material in this post, you can create another struct from the first, then implement the interface on that struct. See this [playground example](https://play.golang.org/p/sDFBros3Wm_6). – theherk Aug 15 '18 at 16:23
  • hi @TheHerk, What i'm aim is to you to point out another difference when "extending" a struct from another package. Looks like to me, that there are two ways to archeive this, by using type alias (your example) and using type embed (https://play.golang.org/p/psejeXYbz5T). For me it looks like that type alias make easier the conversion as you only need a *type conversion*, if you use type wrap you need to reference the "parent" struct by using a dot, thus, accessing the parent type itself. I guess is up to the client code... – Victor Aug 16 '18 at 18:11
  • please, see the motivation of this topic here https://stackoverflow.com/a/28800807/903998, follow the comments and i hope you will see my point – Victor Aug 16 '18 at 18:12
  • I wish I could follow your meaning, but I'm still having trouble. In the answer upon which we are writing these comments, I explain both embedding and aliasing, including the advantages and disadvantages of each. I'm not advocating for one over the other. It may be that your are suggesting I missed one of those pros or cons. – theherk Aug 21 '18 at 22:50
  • As far as having to use the dot to reference the embedded child, you only have to do so, if the parent doesn't contain the method or attribute being referenced, which avoids ambiguity in those cases. See [this playground example](https://play.golang.org/p/O5FGX9p3Iyf) where I added a call to a method in the embedded type without using the dot notation to reference that type. – theherk Aug 21 '18 at 22:51
  • The alias does allow access to the methods if you use type assertion. So, for instance, you would do asserted := imported.Thing(myThing) and then call methods on asserted. – Sean F Oct 24 '18 at 21:04
  • How do you coerce the mux.Router instances to MyRouters? e.g. if you have a library that returns mux.Router but you want to use your new methods? – HappyFace Aug 29 '20 at 20:51
  • @HappyFace In that case won't this work? `child := &childPointerParent{&parent{}}` i.e. `parentRouter := function(...); cRouter := &RouterChildPointer{&parentRouter}` now `cRouter.Method(...)` – Phani Rithvij Nov 02 '20 at 00:52
  • 1
    For Method 1 (Type Definition), you can still access the parent's methods. However, you'll need to cast the alias (i.e. child) to parent. `c1 := &childAlias{"cAliasAttr"}` `ptr := (*parent)(c1)` `ptr.parentDo("called parentDo on ChildAlias")` – Aditya Basu Jul 25 '21 at 20:42
  • 1
    @AdityaBasu That is true. You could use type conversion. There are many options in this context. – theherk Jul 27 '21 at 12:40
3

Expanding on one of the other answers, in my case the parent is an array. If you want to add methods, but also have access to the parent methods, you must wrap when defining the type, and wrap when declaring a variable:

package main

type parent []int

func (p parent) first() int {
   return p[0]
}

type child struct {
   parent
}

func (c child) second() int {
   return c.parent[1]
}

func main() {
   a := child{
      parent{1, 2},
   }
   first := a.first()
   second := a.second()
   println(first == 1, second == 2)
}
Zombo
  • 1
  • 62
  • 391
  • 407