| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file shows some examples of generic types. |
| |
| package p |
| |
| // List is just what it says - a slice of E elements. |
| type List[E any] []E |
| |
| // A generic (parameterized) type must always be instantiated |
| // before it can be used to designate the type of a variable |
| // (including a struct field, or function parameter); though |
| // for the latter cases, the provided type may be another type |
| // parameter. So: |
| var _ List[byte] = []byte{} |
| |
| // A generic binary tree might be declared as follows. |
| type Tree[E any] struct { |
| left, right *Tree[E] |
| payload E |
| } |
| |
| // A simple instantiation of Tree: |
| var root1 Tree[int] |
| |
| // The actual type parameter provided may be a generic type itself: |
| var root2 Tree[List[int]] |
| |
| // A couple of more complex examples. |
| // We don't need extra parentheses around the element type of the slices on |
| // the right (unlike when we use ()'s rather than []'s for type parameters). |
| var _ List[List[int]] = []List[int]{} |
| var _ List[List[List[Tree[int]]]] = []List[List[Tree[int]]]{} |
| |
| // Type parameters act like type aliases when used in generic types |
| // in the sense that we can "emulate" a specific type instantiation |
| // with type aliases. |
| type T1[P any] struct { |
| f P |
| } |
| |
| type T2[P any] struct { |
| f struct { |
| g P |
| } |
| } |
| |
| var x1 T1[struct{ g int }] |
| var x2 T2[int] |
| |
| func _() { |
| // This assignment is invalid because the types of x1, x2 are T1(...) |
| // and T2(...) respectively, which are two different defined types. |
| x1 = x2 // ERROR assignment |
| |
| // This assignment is valid because the types of x1.f and x2.f are |
| // both struct { g int }; the type parameters act like type aliases |
| // and their actual names don't come into play here. |
| x1.f = x2.f |
| } |
| |
| // We can verify this behavior using type aliases instead: |
| type T1a struct { |
| f A1 |
| } |
| type A1 = struct { g int } |
| |
| type T2a struct { |
| f struct { |
| g A2 |
| } |
| } |
| type A2 = int |
| |
| var x1a T1a |
| var x2a T2a |
| |
| func _() { |
| x1a = x2a // ERROR assignment |
| x1a.f = x2a.f |
| } |
| |
| // Another interesting corner case are generic types that don't use |
| // their type arguments. For instance: |
| type T[P any] struct{} |
| |
| var xint T[int] |
| var xbool T[bool] |
| |
| // Are these two variables of the same type? After all, their underlying |
| // types are identical. We consider them to be different because each type |
| // instantiation creates a new named type, in this case T<int> and T<bool> |
| // even if their underlying types are identical. This is sensible because |
| // we might still have methods that have different signatures or behave |
| // differently depending on the type arguments, and thus we can't possibly |
| // consider such types identical. Consequently: |
| func _() { |
| xint = xbool // ERROR assignment |
| } |
| |
| // Generic types cannot be used without instantiation. |
| var _ T // ERROR cannot use generic type T |
| |
| // In type context, generic (parameterized) types cannot be parenthesized before |
| // being instantiated. See also NOTES entry from 12/4/2019. |
| var _ (T /* ERROR cannot use generic type T */ )[ /* ERROR expected ';' */ int] |
| |
| // All types may be parameterized, including interfaces. |
| type I1[T any] interface{ |
| m1(T) |
| } |
| |
| // There is no such thing as a variadic generic type. |
| type _[T ... /* ERROR invalid use of ... */ interface{}] struct{} |
| |
| // Generic interfaces may be embedded as one would expect. |
| type I2 interface { |
| I1(int) // method! |
| I1[string] // embedded I1 |
| } |
| |
| func _() { |
| var x I2 |
| x.I1(0) |
| x.m1("foo") |
| } |
| |
| type I0 interface { |
| m0() |
| } |
| |
| type I3 interface { |
| I0 |
| I1[bool] |
| m(string) |
| } |
| |
| func _() { |
| var x I3 |
| x.m0() |
| x.m1(true) |
| x.m("foo") |
| } |
| |
| // We accept parenthesized embedded struct fields so we can distinguish between |
| // a named field with a parenthesized type foo (T) and an embedded parameterized |
| // type (foo(T)), similarly to interface embedding. |
| // They still need to be valid embedded types after the parentheses are stripped |
| // (i.e., in contrast to interfaces, we cannot embed a struct literal). The name |
| // of the embedded field is derived as before, after stripping parentheses. |
| // (7/14/2020: See comment above. We probably will revert this generalized ability |
| // if we go with [] for type parameters.) |
| type _ struct { |
| int8 |
| *int16 |
| *List[int] |
| |
| int8 /* ERROR int8 redeclared */ |
| * /* ERROR List redeclared */ List[int] |
| } |
| |
| // It's possible to declare local types whose underlying types |
| // are type parameters. As with ordinary type definitions, the |
| // types underlying properties are "inherited" but the methods |
| // are not. |
| func _[T interface{ m(); type int }]() { |
| type L T |
| var x L |
| |
| // m is not defined on L (it is not "inherited" from |
| // its underlying type). |
| x.m /* ERROR x.m undefined */ () |
| |
| // But the properties of T, such that as that it supports |
| // the operations of the types given by its type bound, |
| // are also the properties of L. |
| x++ |
| _ = x - x |
| |
| // On the other hand, if we define a local alias for T, |
| // that alias stands for T as expected. |
| type A = T |
| var y A |
| y.m() |
| _ = y < 0 |
| } |
| |
| // As a special case, an explicit type argument may be omitted |
| // from a type parameter bound if the type bound expects exactly |
| // one type argument. In that case, the type argument is the |
| // respective type parameter to which the type bound applies. |
| // Note: We may not permit this syntactic sugar at first. |
| // Note: This is now disabled. All examples below are adjusted. |
| type Adder[T any] interface { |
| Add(T) T |
| } |
| |
| // We don't need to explicitly instantiate the Adder bound |
| // if we have exactly one type parameter. |
| func Sum[T Adder[T]](list []T) T { |
| var sum T |
| for _, x := range list { |
| sum = sum.Add(x) |
| } |
| return sum |
| } |
| |
| // Valid and invalid variations. |
| type B0 interface {} |
| type B1[_ any] interface{} |
| type B2[_, _ any] interface{} |
| |
| func _[T1 B0]() |
| func _[T1 B1[T1]]() |
| func _[T1 B2 /* ERROR cannot use generic type .* without instantiation */ ]() |
| |
| func _[T1, T2 B0]() |
| func _[T1 B1[T1], T2 B1[T2]]() |
| func _[T1, T2 B2 /* ERROR cannot use generic type .* without instantiation */ ]() |
| |
| func _[T1 B0, T2 B1[T2]]() // here B1 applies to T2 |
| |
| // When the type argument is left away, the type bound is |
| // instantiated for each type parameter with that type |
| // parameter. |
| // Note: We may not permit this syntactic sugar at first. |
| func _[A Adder[A], B Adder[B], C Adder[A]]() { |
| var a A // A's type bound is Adder[A] |
| a = a.Add(a) |
| var b B // B's type bound is Adder[B] |
| b = b.Add(b) |
| var c C // C's type bound is Adder[A] |
| a = c.Add(a) |
| } |
| |
| // The type of variables (incl. parameters and return values) cannot |
| // be an interface with type constraints or be/embed comparable. |
| type I interface { |
| type int |
| } |
| |
| var ( |
| _ interface /* ERROR contains type constraints */ {type int} |
| _ I /* ERROR contains type constraints */ |
| ) |
| |
| func _(I /* ERROR contains type constraints */ ) |
| func _(x, y, z I /* ERROR contains type constraints */ ) |
| func _() I /* ERROR contains type constraints */ |
| |
| func _() { |
| var _ I /* ERROR contains type constraints */ |
| } |
| |
| type C interface { |
| comparable |
| } |
| |
| var _ comparable /* ERROR comparable */ |
| var _ C /* ERROR comparable */ |
| |
| func _(_ comparable /* ERROR comparable */ , _ C /* ERROR comparable */ ) |
| |
| func _() { |
| var _ comparable /* ERROR comparable */ |
| var _ C /* ERROR comparable */ |
| } |
| |
| // Type parameters are never const types, i.e., it's |
| // not possible to declare a constant of type parameter type. |
| // (If a type list contains just a single const type, we could |
| // allow it, but such type lists don't make much sense in the |
| // first place.) |
| func _[T interface { type int, float64 }]() { |
| // not valid |
| const _ = T /* ERROR not constant */ (0) |
| const _ T /* ERROR invalid constant type T */ = 1 |
| |
| // valid |
| var _ = T(0) |
| var _ T = 1 |
| _ = T(0) |
| } |
| |