# Go 1.18 generics ## What you will learn from this topic * A summary of the release note of go 1.18 generics with source code * More example that didn't provide within official pages ## How to install ``` $ go install golang.org/dl/go1.18rc1@latest $ go1.18rc1 download $ go1.18rc1 Go is a tool for managing Go source code. Usage: go <command> [arguments] ... ``` ## Generics are not 100% generic ### constraints * Any * Define with language level * IT IS INTERFACE{} * PR is here https://github.com/golang/go/issues/33232 * Compareable * Define with language level * follow the defination of https://go.dev/ref/spec#Comparison_operators * == * != * Ordered * Define with constraints package * `<` less * `<= ` less or equal * `>` greater * `>=` greater or equal e.g, interface are not Ordered but Compareable. ```go // Copyright 2021 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. // Package constraints defines a set of useful constraints to be used // with type parameters. package constraints // Signed is a constraint that permits any signed integer type. // If future releases of Go add new predeclared signed integer types, // this constraint will be modified to include them. type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } // Unsigned is a constraint that permits any unsigned integer type. // If future releases of Go add new predeclared unsigned integer types, // this constraint will be modified to include them. type Unsigned interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } // Integer is a constraint that permits any integer type. // If future releases of Go add new predeclared integer types, // this constraint will be modified to include them. type Integer interface { Signed | Unsigned } // Float is a constraint that permits any floating-point type. // If future releases of Go add new predeclared floating-point types, // this constraint will be modified to include them. type Float interface { ~float32 | ~float64 } // Complex is a constraint that permits any complex numeric type. // If future releases of Go add new predeclared complex numeric types, // this constraint will be modified to include them. type Complex interface { ~complex64 | ~complex128 } // Ordered is a constraint that permits any ordered type: any type // that supports the operators < <= >= >. // If future releases of Go add new ordered types, // this constraint will be modified to include them. type Ordered interface { Integer | Float | ~string } ``` Reference: https://cs.opensource.google/go/x/exp/+/bbda1eaf:constraints/constraints.go ## What can we do? ### return max value ```go package main import "fmt" import "golang.org/x/exp/constraints" func Max[Elem constraints.Ordered](a Elem, b Elem) Elem { if a >= b { return a } return b } func main() { fmt.Println(Max(1, 2)) fmt.Println(Max(2.2, 1.1)) } //go1.18rc1 run main.go //2 //2.2 ``` ### Insert multiple value into slice ```go package main import "fmt" func Insert[S ~[]E, E any](s S, i int, v ...E) S { s2 := make(S, len(s) + len(v)) copy(s2, s[:i]) copy(s2[i:], v) copy(s2[i+len(v):], s[i:]) return s2 } func main() { s1 := []int{1, 2, 3, 4} s2 := Insert(s1, 1, 5, 6, 7) fmt.Println(s2) } // go1.18rc1 run main.go // [1 5 6 7 2 3 4] ``` ```go package main import "fmt" func Insert[S ~[]E, E interface{}](s S, i int, v ...E) S { s2 := make(S, len(s)+len(v)) copy(s2, s[:i]) copy(s2[i:], v) copy(s2[i+len(v):], s[i:]) return s2 } func main() { s1 := []int{1, 2, 3, 4} s2 := Insert(s1, 1, 5, 6, 7) fmt.Println(s2) } ``` ### BinarySearch ```go func BinarySearch[Elem constraints.Ordered](x []Elem, target Elem) int { return search(len(x), func(i int) bool { return x[i] >= target }) } func search(n int, f func(int) bool) int { // Define f(-1) == false and f(n) == true. // Invariant: f(i-1) == false, f(j) == true. i, j := 0, n for i < j { h := int(uint(i+j) >> 1) // avoid overflow when computing h // i ≤ h < j if !f(h) { i = h + 1 // preserves f(i-1) == false } else { j = h // preserves f(j) == true } } // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. return i } ``` ### Sort ```go package main import "fmt" import "golang.org/x/exp/constraints" func Sort[Elem constraints.Ordered](x []Elem, f func(a, b Elem) bool) { n := len(x) sortFunction(x, 0, n, f) } // insertionSortLessFunc sorts data[a:b] using insertion sort. func sortFunction[Elem any](data []Elem, a, b int, less func(a, b Elem) bool) { for i := a + 1; i < b; i++ { for j := i; j > a && less(data[j], data[j-1]); j-- { data[j], data[j-1] = data[j-1], data[j] } } } func comp(a int64, b int64) bool { if a <= b { return true } return false } func main() { s1 := []int64{1, 9, 5, 2} fmt.Println(s1) Sort(s1, comp) fmt.Println(s1) } //go1.18rc1 run main.go //[1 9 5 2] //[1 2 5 9] ``` ## Can interface do the samething? Yes, just a little uglier and more complicated **And not type safe** ### Insert multiple value into slice ```go package main import "fmt" func Insert(s []interface{}, i int, v ...interface{}) []interface{} { s2 := make([]interface{}, len(s)+len(v)) copy(s2, s[:i]) copy(s2[i:], v) copy(s2[i+len(v):], s[i:]) return s2 } func main() { s := []int{1, 2, 3, 4} s1 := make([]interface{}, len(s), len(s)) for i := range s { s1[i] = s[i] } s2 := Insert(s1, 1, 5, 6, 7) fmt.Println(s2) } // output [1 5 6 7 2 3 4] ``` Seems works? Let's try to use this function like this ``` go func main() { s := []int{1, 2, 3, 4} s1 := make([]interface{}, len(s), len(s)) for i := range s { s1[i] = s[i] } s2 := Insert(s1, 1, 5, 0.0, []int{1,2,3}) fmt.Println(s2) s3 := make([]int, len(s2)) for i := range s2 { s3[i] = s2[i].(int) } } //go run main.go // //panic: interface conversion: interface {} is float64, not int //goroutine 1 [running]: //main.main() // /Users/rance/go/src/github.com/rancejen/generic_test/main.go:22 +0x388 //exit status 2 ``` **How about generic?** ```shell $ go1.18rc1 run main.go # command-line-arguments ./main.go:15:14: []int does not implement [][]int ./main.go:15:22: cannot use 5 (untyped int constant) as []int value in argument to Insert ./main.go:15:25: cannot use 6 (untyped int constant) as []int value in argument to Insert ./main.go:15:28: cannot use 7 (untyped int constant) as []int value in argument to Insert ``` **It can detect problem in compile time** ### binary search ```go package main import ( "fmt" ) func BinarySearch(x []interface{}, f func(int) bool) int { return search(len(x), f) } func search(n int, f func(int) bool) int { // Define f(-1) == false and f(n) == true. // Invariant: f(i-1) == false, f(j) == true. i, j := 0, n for i < j { h := int(uint(i+j) >> 1) // avoid overflow when computing h // i ≤ h < j if !f(h) { i = h + 1 // preserves f(i-1) == false } else { j = h // preserves f(j) == true } } // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. return i } func main() { s := []int{1, 3, 5, 7, 9, 11} s1 := make([]interface{}, len(s), len(s)) for i := range s { s1[i] = s[i] } fmt.Println(BinarySearch(s1, func(i int) bool { return s1[i].(int) >= 11 })) } // go run main.go // [1 5 6 7 2 3 4] ``` ### Limitation reference https://tip.golang.org/doc/go1.18#generics 1. The Go compiler cannot currently handle type declarations inside generic functions or methods. We hope to provide support for this feature in Go 1.19. ``` func Max[Elem constraints.Ordered](a Elem, b Elem) Elem { type c int if a >= b { return a } return b } //go1.18rc1 run main.go //./main.go:7:2: type declarations inside generic functions are not currently supported ``` 2. The Go compiler currently does not accept arguments of type parameter type with the predeclared functions real, imag, and complex. We hope to remove this restriction in Go 1.19. I can't get it. 3. The Go compiler currently only supports calling a method m on a value x of type parameter type P if m is explicitly declared by P's constraint interface. Similarly, method values x.m and method expressions P.m also are only supported if m is explicitly declared by P, even though m might be in the method set of P by virtue of the fact that all types in P implement m. We hope to remove this restriction in Go 1.19. 4. Embedding a type parameter, or a pointer to a type parameter, as an unnamed field in a struct type is not permitted. Similarly, embedding a type parameter in an interface type is not permitted. Whether these will ever be permitted is unclear at present. 5. A union element with more than one term may not contain an interface type with a non-empty method set. Whether this will ever be permitted is unclear at present. ## This doesn't work ```go type vector struct { values []any } func main { v := vector{[]int{1,2,3}} } ```