mofury
mofury

Reputation: 725

With Go 1.18 generics, how to use constrained type as argument to function that expects a concrete type?

Go version: 1.18

Here's a silly example that is not particularly useful. I am using this as an exercise to learn generics.

I have a Pokemon interface

type Pokemon interface {
    ReceiveDamage(float64)
    InflictDamage(Pokemon)
}

and Charmander with type parameter that implements the Pokemon interface.

type Float interface {
    float32 | float64
}

type Charmander[F Float] struct {
    Health      F
    AttackPower F
}

I want to use Charmander's attack power to inflict damage.

func (c *Charmander[float64]) ReceiveDamage(damage float64) {
    c.Health -= damage
}

func (c *Charmander[float64]) InflictDamage(other Pokemon) {
    other.ReceiveDamage(c.AttackPower)
}

My compiler gives error

cannot use c.AttackPower (variable of type float64 constrained by Float) as float64 value in argument to other.ReceiveDamage compiler(IncompatibleAssign)

I already instantiated the struct generic as *Charmander[float64]. I'd expect that the compiler knows AttackPower is a float64.

When I pass a float64 into a function that expects float64, why should it complain? On the other hand, ReceiveDamage does not complain. I am subtracting a float64 from Health which is a constrained type.

Upvotes: 7

Views: 2956

Answers (1)

blackgreen
blackgreen

Reputation: 44698

You have to use type conversions. The method ReceiveDamage expects a float64 but the main type is parametrized in F. Something of type F, even if constrained to floats only, or even if constrained to one specific float, is not float64. It is F. (Moreover, it could also be instantiated with float32).

Both conversions compile because float64 is convertible to all types in the type parameter's type set, float32 and float64, and vice-versa.

So the methods become:

func (c *Charmander[T]) ReceiveDamage(damage float64) {
    c.Health -= T(damage)
}

func (c *Charmander[T]) InflictDamage(other Pokemon) {
    other.ReceiveDamage(float64(c.AttackPower))
}

Fixed playground: https://go.dev/play/p/FSsdlL8tBLn

Watch out that the conversion T(damage) may cause loss of precision when T is instantiated with float32. (Probably this won't be an issue in this specific use case...)

Upvotes: 4

Related Questions