Description
The Problem
Let's make an interface called Cloner
, which describes things that can clone themselves.
As any good Go interface should be designed, it should describe behaviors rather than making implementations conform to the interface.
So, let's see our implementations:
type Foo struct { ... }
func (f *Foo) Clone() *Foo {
newF := new(Foo)
...
return newF
}
type Bar struct {...}
func (b *Bar) Clone() *Bar {
// same as above, but with Bar
}
As you can see, the two Clone()
methods have separate signatures. This is unfortunate as this means it is impossible to have an interface describe both types, even if they have similar patterns.
Current Solution
We could "solve" this issue like so:
type Cloner interface {
Clone() Cloner
}
Then, we'd design the Foo
and Bar
functions to return Cloner
s rather than themselves.
This causes a few issues, though.
- Since
Cloner
s are returned, this means callingClone()
directly on aFoo
orBar
would return aCloner
, which means we need to cast back to Foo, which is a big readability issue. - This is also less efficient when using
Foo
andBar
directly, as it causes unnecessary boxing/unboxing, although I'm not sure if the compiler optimizes this away. - This is also an anti-pattern, as we are designing our implementations around the interface, rather than making an interface that describes the implementations.
This is also apparently not the most intuitive solution, as it needs to be answered in the FAQ: https://golang.org/doc/faq#t_and_equal_interface
This is "solved" by generics, although not really. This is detailed at the bottom of the proposal.
The proposed solution: self
Instead, our Cloner interface would look as follows:
type Cloner interface {
Clone() self
}
The implementations would not need to conform to the interface, so Foo.Clone
would remain func (f *Foo) Clone() *Foo
, and Bar.Clone
would also remain the same.
Then, it could be used as follows:
func OurTestFunction(foo Foo, bar Bar, cloner Cloner) {
fooClone := foo.Clone() // fooClone is type `Foo`
barClone := bar.Clone() // barClone is type `Bar`
someClone := cloner.Clone() // someClone is type `Cloner`
}
Bada-bing Bada-boom, we've solved our issue with relative simplicity.
Footnotes
Footnote: Syntax and compatability
"self" would also be a predeclared type, so if anyone has a type named "self" in their package, then that will be used rather than the predeclared self
.
This goes without saying, but we aren't locked to the name "self" or anything. Also, adding a special character to show that the "self" type is special, such as @self
or #self
, may be a good idea. I am personally not a fan of this, as it don't feel very Go-like.
Footnote: Generics
I referenced above that the issue is partially solved by generics. This section illustrates how generics can solve some issues, but is not a full solution.
Here are the two solutions which can be used to solve our problem:
// Solution 1:
contract Cloneable(t T) {
var t2 T = t.Clone()
}
// Solution 2 (if you really want an interface which can be evaluated at runtime):
type Cloner(type T) interface {
Clone() T
}
Solution 1 prevents you from checking if t
is Cloneable at runtime.
Solution 2 works, although it suffers from "Generic Poisoning" as then every function that wants to take a Cloner
as an argument would then need parametric types.
Solution 2 ALSO suffers from the fact that T
is not guaranteed to be the same as the receiver, leading to the following also implementing Cloner
:
// A human
type Human struct { ... }
// A physical clone of a human. Human clones are not considered humans under our ethics board.
type HumanClone struct { ... }
func (h *Human) Clone() *HumanClone {
// ...
}
This is not a true Cloner
implementation! It returns a separate type from its receiver.