What does the ~ (tilde) token mean in a constraint? What happens without it?
Question 115MediumGo 1.22 to 1.25
~T means "every type whose underlying type is T". Without the tilde, only the exact type T itself matches. Named types such as type Celsius float64 or time.Duration (underlying int64) are then rejected.
type Exact interface{ int64 }
type Approx interface{ ~int64 }
func A[T Exact](v T) T { return v }
func B[T Approx](v T) T { return v * 2 }
// A(time.Second) // error: time.Duration does not satisfy Exact
fmt.Println(B(time.Second)) // 2s (result is time.Duration, not int64)
type Name string
func Upper[T ~string](s T) T { return T(strings.ToUpper(string(s))) }
fmt.Printf("%T\n", Upper(Name("go"))) // main.Name
Rules and gotchas:
- In
~T, T must be its own underlying type.~time.Durationis illegal, and so is~erroror any other interface. - Inside the function you usually need an explicit conversion to call non-generic APIs, as in
string(s)above. The result then keeps the caller's named type. - Library constraints such as
cmp.Ordereduse~everywhere. That is why sorting works on named types.
Interviewers want to hear that you understand the difference between underlying type and named type, and why the result type is preserved.
More on Generics
- Q113What is the difference between
func F(s fmt.Stringer)andfunc F[T fmt.Stringer](s T)? When does it matter? - Q114What is a constraint in Go generics? Show the different ways to write one, including the
[P *C]parsing gotcha. - Q116Explain type sets. How do unions and intersections combine, and which interfaces can only be used as constraints?
- Q117What is
comparable? What changed in Go 1.20, and what does this program print? - Q118Which operations can you perform on a value of type parameter type? Why can't you access a struct field even when every type in the set has it?
- Q119How does type inference work? When does it fail, and how do you order type parameters to make partial instantiation pleasant?