2

(As a followup this question: nested struct initialization literals).

Now that I can initialize a struct with literals that are easy to write, I later in my code need access members of the parent struct, but without knowing the concrete derived type. It's like this:

type A struct {
    MemberA string
}

type B struct {
    A
    MemberB string
}

And then I use it like this:

b := B {
    A: A { MemberA: "test1" },
    MemberB: "test2",
}
fmt.Printf("%+v\n", b)

var i interface{} = b

// later in the code, I only know that I have something that has a nested A,
// I don't know about B (because A is in a library and B is in the 
// calling code that uses the library).

// so I want to say "give me the A out of i", so I try a type assertion

if a, ok := i.(A); ok {
    fmt.Printf("Yup, A is A: %+v\n", a)
} else {
    fmt.Printf("Aristotle (and John Galt) be damned! A is NOT A\n")
}

// no go

The options I see are:

  • I could use reflection to look for a member called "A" and, assuming it's the right type, use it. This would be workable but less efficient and certainly more "clunky".

  • I could require the caller to implement an interface (like HasA { Aval() A } or similar which returns an instance of A. So far this is the best idea I could think of.

  • The other point is that I could just have the caller pass a A value (i.e. in the example above, var i interface{} = b becomes var i A = b.A). But what's happening is I actually dynamically iterate over the members of B and do stuff with them, so I need that more "derived" type in order to that. (I've omitted that from the question because it's more just background as to why I'm running into this and is not pertinent to the technical answer of the question.)

It would be great if I could just "cast it to A", as you would in Java. Is there a more elegant way to do that.

Community
  • 1
  • 1
Brad Peabody
  • 10,917
  • 9
  • 44
  • 63

4 Answers4

2

If you have an unknown type b, the only way to dig out an embedded field is through reflection.

It's not that clunky:

// obviously missing various error checks
t := reflect.ValueOf(i)
fmt.Printf("%+v\n", t.FieldByName("A").Interface().(A))

Struct embedding is not inheritance, and trying to use it as such is going to continue to bring up issues like this. The way to achieve general polymorphism in go is to use interfaces.

I think the cleanest way to handle this situation is to use a common interface, with appropriate accessor methods for the fields you want to handle. You'll see examples of this the stdlib, e.g. http.ResponseWriter, which has a Header() method used to access the actual response headers.

JimB
  • 104,193
  • 13
  • 262
  • 255
  • Thanks Jim - you're right. The problem itself and trying to solve it by nesting structs is more inherently clunky than the solution - this snippet is quite manageable given the problem. Got it on the other uses of the interface pattern in std, that's a good point too. – Brad Peabody Oct 11 '13 at 21:40
2

It would be great if I could just "cast it to A", as you would in Java. Is there a more elegant way to do that.

Blind casting is almost always bad news for your code quality so it is actually quite good to make it difficult to do so.

I would go the interface route as it would also eliminate the interface{} you use to store b. If you indeed have a high variety of types and they only share that they embed A an interface which offers AVal() A seems good to me.

As a bonus, you could define AVal() A on A directly so you don't need to implement it for every type that embeds A.

Example (on play):

type A struct {
    MemberA string
}

func (a *A) AVal() *A {
    return a
}

type B struct {
    *A
    MemberB string
}

type AEmbedder interface {
    AVal() *A
}

func main() {
    b := B {
            A: &A { MemberA: "test1" },
            MemberB: "test2",
    }

    var i AEmbedder = b

    a := i.AVal()
    fmt.Printf("Yup, A is A: %+v\n", a)
}

Note that I'm now using a pointer value so that AVal() *A does not return a copy but the respective instance of A.

nemo
  • 55,207
  • 13
  • 135
  • 135
  • I see. Since A implements AEmbedder, then B, by nesting does as well... That seems pretty workable and simple. It also means that when you do `var i AEmbedder = b` the run time type of the value in `i` is still B - so iterating over it's fields etc. gives me results from the more derived time. Pretty rad. I think this is exactly what I want. (Unless I can avoid this altogether, as you mentioned.) – Brad Peabody Oct 11 '13 at 21:31
0

This worked for me. Although it's not directly casting like you wanted, it does give the correct object instance

fmt.Printf("It's", i.(B).A)

I hope it helps!

bclymer
  • 6,679
  • 2
  • 27
  • 36
0

Playing around with it for a bit, I got this to work: http://play.golang.org/p/-bbHZr-0xx

I'm not 100% sure on why this works, but my best theory is that when you call

a := i.(A)

you are trying to do an interface version of a typecast of whatever is stored in i to A. So you first need to tell it that it is actually B, then you can access the A that is nested in it

a := i.(B).A
Verran
  • 3,942
  • 2
  • 14
  • 22
  • You assert `i` to be `B` using `i.(B)`. After that you can request `A` from that asserted value as it is a member of `B`. No magic here. Sadly, this requires you to test `i` for every possible type, as `B` might not be the only type having an `A`. Hence this method may not be the best. – nemo Oct 11 '13 at 21:22
  • @nemo - yes, that is the exactly the difficulty I'm running into. I don't want that code to "know" about B, because B is actually created in another package that is using the package where A lives as a library. – Brad Peabody Oct 11 '13 at 21:26
  • The best options then are a) avoiding this situation, b) use interfaces c) use reflection (in that order). – nemo Oct 11 '13 at 21:28