Search code examples
gogenericsvariable-assignmentinstantiation

Why a generic can't be assigned to another even if their type arguments can?


Following code throws a compilation error

cannot use ExampleProps (variable of type Props[Example]) as Props[Generic] value in return statement

// Abstract
type Generic interface {
    ID() string
}

type Props[G Generic] struct{}

// Example
type Example struct {
    id string
}

func (example Example) ID() string {
    return example.id
}

var ExampleProps = Props[Example]{}

// Problem
func Problem() Props[Generic] {
    return ExampleProps
}

My question is: as Example implements Generic, why does Go not allow assigning Props[Example] to Props[Generic]?


Solution

  • Instantiating a generic type with different type arguments produces two new different named types.

    Note that every time you supply a type argument, including in function arguments or return types, you are instantiating the generic type:

    // Props is instantiated with type argument 'Generic'
    func Problem() Props[Generic] {
        return ExampleProps
    }
    

    Therefore Props[Example] is just not the same type as Props[Generic] and you can't use values of one type wherever the other one is expected. It does not matter if the types used as arguments themselves meet some condition for assignability, e.g. interfaces and implementors.

    This is also true of generics instantiated with any. The type any is just another static type — alias of interface{}. It's not equal to T and it's not equal to "whatever type".

    In simpler terms it’s as if you were using int where string is expected.

    What you can do fix it and keep some flexibility is to instantiate Props with a type parameter — whether this makes sense or not depends on how you actually plan to use this function. Anyway, as demonstration:

    // adding a field to make this a bit less contrived
    type Props[G Generic] struct{ Value G }
    
    // Props instantiated with T, adequately constrained
    func Problem[T Generic](v T) Props[T] {
        return Props[T]{ Value: v }
    }
    
    func main() {
        a := Problem(Example{})
        fmt.Println(a)
    }
    

    Playground: https://gotipplay.golang.org/p/wcDOtJ6z80u