How to add new methods to an existing type in Go?

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?


Solution 1:

As the compiler mentions, you can't extend existing types in another package. You can define your own alias or sub-package 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()

Solution 2:

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")
}

Solution 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)
}