Step 1 of 5 · Reading · ~4 min
Learn
Generics and Reflection
Generics
Generics let one function or type work for many element types, with every use checked by the compiler. You will read type parameters and constraints, then write the code they replace.
In Go Intermediate you stored mixed values as interface{} and got them back with a type assertion or switch, a check that fails at run time. Generics move it to compile time.
Grader note: Go 1.13 has no type parameters, so Go 1.18+ blocks are read-only.
Type parameters and inference
A type parameter is a placeholder type in square brackets before the ordinary parameters; its constraint says which types may fill it.
The compiler infers T = string from the arguments.
Before 1.18 you copied Index per type or took []interface{}, which accepts Index([]interface{}{1, "b"}, 2.0) and silently returns -1.
Constraints are interfaces
With any the body may only do what every type allows: no <, no +. A richer constraint lists a type set, the types it allows:
~float64 means "any type whose underlying type is float64", so Ratio qualifies. Before 1.18 a type set meant a type switch, which matches the exact dynamic type. This runs on the grader:
Seconds is declared type Seconds int, yet it is a different case from int. The repair is case int, float64, Seconds:, a type set typed out by hand, repeated in every switch and extended for every new named type. A constraint states the set once, and ~ covers types not yet written.
There is a second cost. In a combined case int, Seconds: the variable keeps the type interface{}, so += on it does not compile; you write one case per type, each with its own conversion.
comparable admits the types that support ==, which are exactly the types usable as map keys:
map[interface{}]int accepts a []int key and panics on use: hash of unhashable type []int.
Generic types and their limits
A generic type such as type Stack[T any] struct{ items []T } has methods that repeat the receiver's type parameters, as in func (s *Stack[T]) Push(x T), but cannot declare new ones: func (s *Stack[T]) Map[U any] is a syntax error, so write a plain function instead.
Use a type parameter when one algorithm runs over different element types and the type must survive the call. Use an interface for behaviour (io.Reader): a generic that only calls one method adds syntax and nothing else.
The compiler makes one copy per GC shape (memory layout), and all pointer types share one, so generic code is not automatically as fast.
Common mistakes
- Listing only the base types. A switch with
intandfloat64skips every named type built on them, so a total or a listing comes out short with no error. - Guarding only insertion. Lookups and deletes with an unhashable key panic too; the
recoverfrom Defer must wrap every map operation. - Collapsing kinds. Interface equality compares dynamic type and value, so int
5, string"5"and aSecondsof 5 are three elements; storing text or converting to the base type merges them. - Trusting map order and
Println. Map order is random, so gather by type and sort each group.Println(1000000.0)prints1e+06;strconv.FormatFloat(x, 'f', -1, 64)gives1000000.
Your exercise
Finish the Set the pre-generics way: a map[interface{}]bool holding ints, strings, floats, bools and two named types (Meters over int, Celsius over float64) answering unhashable for a slice and totalling every numeric element. The case int and case Meters you write side by side in Total are what ~int would replace in a constraint. The starter does the reading and printing.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…