'One type is coerced into another, can a method to determine the type of the receiver?

If types T1 and T2 are based on type T, and type T only comes into existence from a NewT1() or NewT2(), is there any way a function func (*T) WhoAmI() can know whether it "really" is a T1 or T2?

package main

import "fmt"
import "reflect"

type T struct{ s string }

func (v *T) WhoAmI() string {

    // pull type name with reflect
    fmt.Println(reflect.TypeOf(v).Elem().Name()) // always prints "T"!

    // todo: if I am actually T1
    return "T1"
    // todo: else if I am actually T2
    return "T2"
}

type T1 T

func NewT1(s string) T1 { return T1{s} }

type T2 T

func NewT2(s string) T2 { return T2{s} }

func main() {
    var t1 = T1{"xyz"}
    var t2 = T2{"pdq"}
    s1 := ((*T)(&t1)).WhoAmI() // would like to return "T1"
    s2 := ((*T)(&t2)).WhoAmI() // would like to return "T2"
    fmt.Println(s1, s2)
}

to speak technically:

once t1 type T1 is coerced into type T so func (*T) WhoAmI() can be called, does t1 completely lose the fact that its type is really T1? if not, how do we reclaim the knowledge from the perspective of a method receiving type T?

to speak generally:

in other words, if one type is based on another, if a variable of the derived type is coerced into the base type to run a method, can that method learn the real type of the receiver who called it?



Solution 1:[1]

No, it's not possible. Creating a new type from an old one is not like creating a new class that inherits from a parent class in an class-based language. In your case T knows nothing about either T1 or T2 and if you're calling the WhoAmI method you have a receiver of type T by definition.

Your design might work better with an interface. Try something more like this:

type T interface {
    WhoAmI() string
}

type T1 struct {
    s string
}

func (t *T1) WhoAmI() string { return "T1" }

type T2 struct {
    s string
}

func (t *T2) WhoAmI() string { return "T2" }

Try it on the Go playground

T1 and T2 both implement the interface T, so they can be used as type T.

Solution 2:[2]

Evan's answer is good. However, there are multiple ways to solve this problem that are closer to what you were looking for.

When you convert, you actually change types, there's nothing residual. Go only cares what the current type is.

One approach to get around this is just to write a function. Functions are very useful for having shared implementation. Some object oriented languages threw them out as being impure, but they don't know what they're missing (I'm looking at you public static void!).

func WhoAmI(v interface{}) string {
    switch v.(type) {
    case *T: return "*T"
    case *T1: return "*T1"
    case *T2: return "*T2"
    }

    return "unknown"
}

Now you don't have to convert the value in order to call the method/function. Of course, if you're going to do a type switch and do something different for each type, you might as well just write a different method for each type.

To make it a method, you could do:

type T struct { s string }
func (t *T) WhoAmI() string { return WhoAmI(t) }

type T1 T
func (t1 *T1) WhoAmI() string { return WhoAmI(t1) }

This way, you don't need to reimplement the method.

If you really want T to know itself, give it a self! There are two ways of doing this. One is as a parameter:

func (t *T) WhoAmI(self interface{}) string { ... }
...
fmt.Println(t.WhoAmI(t))
fmt.Println(((*T)(t1)).WhoAmI(t1))

The upside of this is that you don't need to do any extra work. The method has access to both t and self, so it has the best of both worlds. However, this becomes part of your interface, which is a little awkward.

You could also make it a field:

type T struct { self interface{} }
func NewT() *T {
    t := new(T)
    t.self = t
    return t
}

type T1 T
func NewT1() *T1 {
    t1 := new(T1)
    t1.self = t1
    return t1
}

Now, any methods on a T or T1 can tell what the object was originally created as by checking self.

You can keep converting back and forth to get methods, or you can use a feature called embedding:

type T struct{}
func (t *T) M() {}

type T1 struct { T }
...
var t T
var t1 T1
t.M()
t1.M()

As you can see, you can call T.M via t or t1. However, keep in mind that T.M will always only see a T, regardless of what you call it on (t or t1). You'll have to use one of the above strategies in order for T.M to be able to see a T1.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 RoninDev
Solution 2