I've inherited some code that looks like this:
type FooWrapper struct {
Stuffer interface{ GetStuff() *grpc.Stuff }
Thinger interface{ GetThing() *grpc.Thing }
Widgeter interface{ GetWidget() *grpc.Widget }
// many more like these
}
func NewFooWrapper(v proto.Message) FooWrapper {
var w FooWrapper
w.Stuffer, _ = v.(interface{ GetStuff() *grpc.Stuff })
w.Thinger, _ = v.(interface{ GetThing() *grpc.Thing })
w.Widgeter, _ = v.(interface{ GetWidget() *grpc.Widget })
// many more like these
return w
}
func (w FooWrapper) GetStuff() *grpc.Stuff {
if w.Stuffer == nil {
return nil
}
return w.Stuffer.GetStuff()
}
// many more methods like this one
We can see that this code does the following:
FooWrapper
struct with a bunch of anonymous interface fields, one for each method that can possibly exist in any implementation of proto.Message
.NewFooWrapper
constructor is converting v
to each one of those anonymous interface types, discarding the error. Thus, if the type boxed in v
does not have the GetXXX
method, the related field in w
will simply be nil
FooWrapper
getters check if the corresponding field is nil
and if it's not, it invokes the method on the boxed value.To me this seems a quite verbose way of implementing a type switch, though I'm not sure this is idiomatic Go code.
However I guess it could be useful in cases where v
had to be passed to multiple unrelated methods, causing the type switch to be copy-pasted everywhere (it's not the case of the code I got here).
Is this code equivalent to a type switch, in practice?
What are the advantages in using this pattern instead of a type switch?
In a word, "no", it's not idiomatic. But of course that doesn't mean it's "wrong".
Although given that the anonymous interface types are repeated, it seems pretty silly to do that, rather than a named type.
If I had inherited that code, I would immediately change it.
And with that exact code sample in mind, I would also re-define my struct to use embedded interfaces:
type Stuffer interface { GetStuff() *grpc.Stuff }
type Thinger interface { GetThing() *grpc.Thing }
type Widgeter interface { GetWidget() *grpc.Widget }
type FooWrapper struct {
Stuffer
Thinger
Widgeter
// many more like these
}