Learning GoLang - Day 5 : Common Interfaces and Generics
To better use interfaces, you have to know more examples.
So, today I started to learn several useful and common interfaces in the standard library of Golang. Here is a short list of them, which are highly suggested to remember:
- fmt.Stringer
- builtin.Error
- io.Reader
- io.Writer
- io.ReadWriteCloser
- http.ResponseWriter
- http.Handler (A more comprehensive listing...)
Stringer
type Stringer interface {
String() string
}
Any type who has a method of String() string
would implement this interface.
For example:
type Book struct {
Title string
Author string
}
func (b Book) String() string {
return fmt.Sprintf("Book: $s - %s", b.Title, b.Author)
}
Benefits
Wherever you see declaration in Go (such as a variable, function parameter or struct field) which has an interface type, you can use an object of any type so long as it satisfies the interface. ---- ALEX EDWARDS
Due to this distinctive characteristic of interfaces in Golang, any function utilizing the fmt.Stringer
interface type as its parameter can accommodate any object that meets the fmt.Stringer
interface criteria. This can be considered a form of genericity.
As outlined by Alex Edwards, there are three compelling reasons to utilize it:
- Minimization of boilerplate code
- Facilitation of unit testing and mocking
- Enhancement of application architecture: promoting flexibility and decoupling
You are free to check his blog to read more about it.
Empty Interface
An interface type in Go is kind of like a definition. It defines and describes the exact methods that some other type must have. ---- ALEX EDWARDS
When you see interface{}
in a declaration, you can use an object of any type. Here is an example:
func main() {
person := make(map[string]interface{}, 0)
person["name"] = "Alice"
person["age"] = 21
person["height"] = 167.64
fmt.Printf("%+v", person)
}
However, if you want to retrieve the value from an empty interface type, you have to type assert the value back to its original type before using it. For example, error will occur when you do person["age"] = person["age"] + 1
. Explicit age, ok := person["age"].(int)
allows you take age
as an int
type.
Even though, it is not suggested to use empty interfaces often, you could always define a specific struct with relevant typed fields for the above cases. It is useful only in situations where you need to work with unpredictable or user-defined types.
In the modern Go codebases(after Go 1.18), a new predeclared identifier any
is introduced, it is actually a syntactic sugar that equivalent to interface{}
.
Generics
Speaking of generics, you need to know type parameters. Type parameters can be used for functions or structures. For example, func Index[T comparable](s []T, x T) int
is a function signature, and [T comparable]
declares the type parameter and its property comparable
. I am very impressed by the built-in approaches(or called constraints here). comparable
constraint means the values of this type should support !=
and ==
operators. It helps to define the necessary behaviors of the introduced type.
And more generic types are supported simply by [T any]
.
Today's lesson has provided us with ample learning material. Tomorrow, I intend to delve into a new chapter, focusing more on the concurrency features of Golang.