Search code examples
gointerfacetype-conversiontype-switch

Is conversion to multiple anonymous interfaces equivalent to a type switch?


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:

  1. It declares a FooWrapper struct with a bunch of anonymous interface fields, one for each method that can possibly exist in any implementation of proto.Message.
  2. The 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
  3. 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?


Solution

  • 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
    }