iTranslated by AI
Practical Guide to Type Constraints in Go Generics: How to Choose the Right Approach
🚀 Practical Guide to Mastering Go Generics Type Constraints
📝 Introduction
Generics were introduced in Go 1.18 and later.
While they brought convenience, many people find themselves unsure about how to write "type constraints."
The following three points are particularly prone to confusion:
- The difference between
~intand~Hoge - The difference between
type Hoge = intandtype Hoge int - How to use a marker interface when you want to allow "only types based on Hoge"
This article will clarify each of these points with code examples and explanations, and finally, summarize "How to choose in practice?" as a decision guideline.
📖 Go Generics and Type Constraint Basics
Type Parameters
Go generics is a mechanism that allows "parameterized types to be passed to functions and types."
func PrintAll[T any](values []T) {
for _, v := range values {
fmt.Println(v)
}
}
-
Tis a type parameter -
anyis a constraint meaning "any type is OK"
Type Constraints
Type parameters can be constrained to specify "what types can be passed." For example, if you want to allow "only integer-like types":
type Integer interface {
int | int64 | uint
}
func Sum[T Integer](a, b T) T {
return a + b
}
-
Integeris the constraint interface -
int | int64 | uintis the "type set"
Type Sets
Constraint interfaces define the "set of types that can be used."
-
any→ All types -
int | string→ int or string -
~int→ All types whose underlying type is int -
~Hoge→ Only valid if Hoge is an alias
📌 1. Difference between ~int and ~Hoge
~int
Allows "all types whose underlying type is int".
type MyInt int
type Another int
type IntFamily interface {
~int
}
👉 OK: int, MyInt, Another
~Hoge
Only valid when Hoge is defined as an alias (type Hoge = int).
type Hoge = int
type Fuga Hoge
type HogeFamily interface {
~Hoge
}
👉 OK: int, Hoge, Fuga
👉 NG: Fails if type Hoge int is used
📌 2. Difference between type Hoge = int and type Hoge int
Defined type
type Hoge int
- Creates a new type
- Different from
int - Uses
~intfor constraints
Type alias
type Hoge = int
- An alias for
int -
~Hogecan be used for constraints - Its actual behavior is exactly the same as
int
📌 3. Controlling with a marker interface
~int allows for
all int-like types.
If you want to exclude int-based types like Piyo, you can use a marker.
type Hoge int
type Fuga Hoge
type Piyo int // ← Exclude this
type HogeFamily interface {
~int
isHogeFamily()
}
// Implement the marker only for Hoge-related types
func (Hoge) isHogeFamily() {}
func (Fuga) isHogeFamily() {}
👉 This way, only Hoge-related types can be targeted by generics.
🙅♂️ Common Misunderstandings and Pitfalls
-
type Hoge intandtype Hoge = intare not the same
→ The former creates a "new type," the latter an "alias." -
~Hogeis not always usable
→ Only for alias types. -
Marker interfaces are not magic
→ They are ineffective unlessisHogeFamily()is implemented for the type.
📊 How to Choose in Practice (Summary)
| What you want to do | Recommended way to write | Practical Recommendation |
|---|---|---|
Allow all int-like types |
~int |
✅ Commonly used |
| Allow only specific series | Marker interface | 🛠 As needed |
Treat as completely identical to int
|
type Hoge = int + ~Hoge
|
⚠️ Rare case |
🎯 Conclusion (Practical Guideline to Avoid Confusion)
- ✅ Start with
type Hoge int+~intas a basic approach. - ⚠️
~Hogeis for special purposes, rarely used in practice. - 🛠 Use markers only when you need to control a specific series.
👉 If in doubt, "type Hoge int + ~int" is fine.
Only consider "markers" or "aliases" when you encounter a special situation.
Reading this article will help you quickly recall the decision criteria when you're unsure about generics type constraints.
Please make use of it in your practical implementations.
Discussion