Since Go lang version 1.18 , expected feature named generics added to the language. We will practice this feature, from old approach in Go before vesion 1.18 (e.g, 1.17 version), then we use new syntax support generics feature. In these below code snippets, we use latest version of Go lang at the time of writing (Go version 1.20.2)
Add student by fullname
Old way (Go lang 1.17): Add student by array, slice without generics
package main import "fmt" func addStudent(students []string, student string) []string { return append(students, student)
} func main() { students := []string{} result := addStudent(students, "Nguyen Thanh Van") result = addStudent(result, "Nguyen Tri Hung") result = addStudent(result, "Pham Van Tam") fmt.Println(result)
} // Result
// [Nguyen Thanh Van Nguyen Tri Hung Pham Van Tam]
Add stutdent by id
package main import "fmt" func addStudent(students []string, student string) []string { return append(students, student)
} func addStudentID(studentIDs []int, studentId int) []int { return append(studentIDs, studentId)
} func main() { studentIDs := []int{} result := addStudentID(studentIDs, 32) result = addStudentID(result, 33) result = addStudentID(result, 34) fmt.Println(result)
} // Result
// [32 33 34]
Add student by struct
package main import "fmt" func addStudentStrut(studentStructs []StudentStruct, st StudentStruct) []StudentStruct{ return append(studentStructs, st)
} type StudentStruct struct{ Name string ID int Age int
} func main() { students := []StudentStruct{} // Empty slice result := addStudentStrut(students, StudentStruct{"Nguyen Dieu An", 1, 35}) result = addStudentStrut(result, StudentStruct{"Tran Van Thuong", 2, 30}) result = addStudentStrut(result, StudentStruct{"Le Van Tam", 3, 25}) fmt.Println(result)
} // Result
// [{Nguyen Dieu An 1 35} {Tran Van Thuong 2 30} {Le Van Tam 3 25}]
Add student by generics
package main import "fmt" type Stringer = interface { String() string
} type Integer int func (i Integer) String() string { return fmt.Sprintf("%d", i)
} type String string func (s String) String() string { return string(s)
} type Student struct { Name string ID int Age int
} func (s Student) String() string { return fmt.Sprintf("%s %d %d", s.Name, s.ID, s.Age)
} func addStudent[T Stringer](students []T, student T) []T { return append(students, student)
} func main() { students := []String{} // Empty slice result := addStudent[String](students, "Nguyen Dieu An") result = addStudent[String](result, "Tran Van Thuong") result = addStudent[String](result, "Le Van Tam") fmt.Println(result) students1 := []Integer{} // Empty slice result1 := addStudent[Integer](students1, 35) result1 = addStudent[Integer](result1, 30) result1 = addStudent[Integer](result1, 25) fmt.Println(result1) students3 := []Student{} // Empty slice result3 := addStudent[Student](students3, Student{"Nguyen Dieu An", 1, 35}) result3 = addStudent[Student](result3, Student{"Tran Van Thuong", 2, 30}) result3 = addStudent[Student](result3, Student{"Le Van Tam", 3, 25}) fmt.Println(result3)
} // Result
// [Nguyen Dieu An Tran Van Thuong Le Van Tam]
// [35 30 25]
// [Nguyen Dieu An 1 35 Tran Van Thuong 2 30 Le Van Tam 3 25]
We need define interface by this way: Any type that implement a String()
function can be considered to be of type Stringer
.
More compact: remove explicit type declaration of generic function
package main import "fmt" type Stringer = interface { String() string
} type Integer int func (i Integer) String() string { return fmt.Sprintf("%d", i)
} type String string func (s String) String() string { return string(s)
} type Student struct { Name string ID int Age int
} func (s Student) String() string { return fmt.Sprintf("%s %d %d", s.Name, s.ID, s.Age)
} func addStudent[T Stringer](students []T, student T) []T { return append(students, student)
} func main() { students := []String{} // Empty slice result := addStudent(students, "Nguyen Dieu An") result = addStudent(result, "Tran Van Thuong") result = addStudent(result, "Le Van Tam") fmt.Println(result) students1 := []Integer{} // Empty slice result1 := addStudent(students1, 35) result1 = addStudent(result1, 30) result1 = addStudent(result1, 25) fmt.Println(result1) students3 := []Student{} // Empty slice result3 := addStudent(students3, Student{"Nguyen Dieu An", 1, 35}) result3 = addStudent(result3, Student{"Tran Van Thuong", 2, 30}) result3 = addStudent(result3, Student{"Le Van Tam", 3, 25}) fmt.Println(result3)
} // Result
// [Nguyen Dieu An Tran Van Thuong Le Van Tam]
// [35 30 25]
// [Nguyen Dieu An 1 35 Tran Van Thuong 2 30 Le Van Tam 3 25]
Loose type by keyword any
in generics function
package main import "fmt" type Student struct { Name string ID int Age int
} func addStudent[T any](students []T, student T) []T { return append(students, student)
} func main() { students := []string{} // Empty slice result := addStudent(students, "Nguyen Dieu An") result = addStudent(result, "Tran Van Thuong") result = addStudent(result, "Le Van Tam") fmt.Println(result) students1 := []int{} // Empty slice result1 := addStudent(students1, 35) result1 = addStudent(result1, 30) result1 = addStudent(result1, 25) fmt.Println(result1) students3 := []Student{} // Empty slice result3 := addStudent(students3, Student{"Nguyen Dieu An", 1, 35}) result3 = addStudent(result3, Student{"Tran Van Thuong", 2, 30}) result3 = addStudent(result3, Student{"Le Van Tam", 3, 25}) fmt.Println(result3)
} // Result
// [Nguyen Dieu An Tran Van Thuong Le Van Tam]
// [35 30 25]
// [Nguyen Dieu An 1 35 Tran Van Thuong 2 30 Le Van Tam 3 25]
Conclusion
We go through these stages:
- Add Student by seperate functions by concrete data type: integer, string, struct
- Define interface, then use Generics
- Use Generics and remove explicit type (compact syntax)
- Use Generics for loosest by using key
any
, it is no contraint to a number of concrete types, for all types. Benefit we seen, source code becomes compact, can re-use easily.