use-modern-go

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Modern Go Guidelines

现代Go语言规范

Detected Go Version

检测到的Go版本

!
grep -rh "^go " --include="go.mod" . 2>/dev/null | cut -d' ' -f2 | sort | uniq -c | sort -nr | head -1 | xargs | cut -d' ' -f2 | grep . || echo unknown
!
grep -rh "^go " --include="go.mod" . 2>/dev/null | cut -d' ' -f2 | sort | uniq -c | sort -nr | head -1 | xargs | cut -d' ' -f2 | grep . || echo unknown

How to Use This Skill

如何使用此技能

DO NOT search for go.mod files or try to detect the version yourself. Use ONLY the version shown above.
If version detected (not "unknown"):
  • Say: "This project is using Go X.XX, so I’ll stick to modern Go best practices and freely use language features up to and including this version. If you’d prefer a different target version, just let me know."
  • Do NOT list features, do NOT ask for confirmation
If version is "unknown":
  • Say: "Could not detect Go version in this repository"
  • Use AskUserQuestion: "Which Go version should I target?" → [1.23] / [1.24] / [1.25] / [1.26]
When writing Go code, use ALL features from this document up to the target version:
  • Prefer modern built-ins and packages (
    slices
    ,
    maps
    ,
    cmp
    ) over legacy patterns
  • Never use features from newer Go versions than the target
  • Never use outdated patterns when a modern alternative is available

请勿自行搜索go.mod文件或尝试检测版本,仅使用上方显示的版本。
如果检测到版本(非“unknown”):
  • 回复:“本项目使用Go X.XX版本,因此我将遵循现代Go最佳实践,自由使用该版本及更低版本的语言特性。如果您希望使用其他目标版本,请告知我。”
  • 请勿列出特性,请勿请求确认
如果版本为“unknown”:
  • 回复:“无法在此仓库中检测到Go版本”
  • 使用AskUserQuestion:“我应该以哪个Go版本为目标?” → [1.23] / [1.24] / [1.25] / [1.26]
编写Go代码时,使用本文档中目标版本及以下的所有特性:
  • 优先使用现代内置函数和包(
    slices
    maps
    cmp
    )而非旧有模式
  • 切勿使用目标版本之后的Go新特性
  • 当有现代替代方案时,切勿使用过时模式

Features by Go Version

各Go版本特性

Go 1.0+

Go 1.0+

  • time.Since
    :
    time.Since(start)
    instead of
    time.Now().Sub(start)
  • time.Since
    :使用
    time.Since(start)
    替代
    time.Now().Sub(start)

Go 1.8+

Go 1.8+

  • time.Until
    :
    time.Until(deadline)
    instead of
    deadline.Sub(time.Now())
  • time.Until
    :使用
    time.Until(deadline)
    替代
    deadline.Sub(time.Now())

Go 1.13+

Go 1.13+

  • errors.Is
    :
    errors.Is(err, target)
    instead of
    err == target
    (works with wrapped errors)
  • errors.Is
    :使用
    errors.Is(err, target)
    替代
    err == target
    (支持包装后的错误)

Go 1.18+

Go 1.18+

  • any
    : Use
    any
    instead of
    interface{}
  • bytes.Cut
    :
    before, after, found := bytes.Cut(b, sep)
    instead of Index+slice
  • strings.Cut
    :
    before, after, found := strings.Cut(s, sep)
  • any
    :使用
    any
    替代
    interface{}
  • bytes.Cut
    :使用
    before, after, found := bytes.Cut(b, sep)
    替代索引+切片的方式
  • strings.Cut
    :使用
    before, after, found := strings.Cut(s, sep)

Go 1.19+

Go 1.19+

  • fmt.Appendf
    :
    buf = fmt.Appendf(buf, "x=%d", x)
    instead of
    []byte(fmt.Sprintf(...))
  • atomic.Bool
    /
    atomic.Int64
    /
    atomic.Pointer[T]
    : Type-safe atomics instead of
    atomic.StoreInt32
go
var flag atomic.Bool
flag.Store(true)
if flag.Load() { ... }

var ptr atomic.Pointer[Config]
ptr.Store(cfg)
  • fmt.Appendf
    :使用
    buf = fmt.Appendf(buf, "x=%d", x)
    替代
    []byte(fmt.Sprintf(...))
  • atomic.Bool
    /
    atomic.Int64
    /
    atomic.Pointer[T]
    :类型安全的原子操作替代
    atomic.StoreInt32
go
var flag atomic.Bool
flag.Store(true)
if flag.Load() { ... }

var ptr atomic.Pointer[Config]
ptr.Store(cfg)

Go 1.20+

Go 1.20+

  • strings.Clone
    :
    strings.Clone(s)
    to copy string without sharing memory
  • bytes.Clone
    :
    bytes.Clone(b)
    to copy byte slice
  • strings.CutPrefix/CutSuffix
    :
    if rest, ok := strings.CutPrefix(s, "pre:"); ok { ... }
  • errors.Join
    :
    errors.Join(err1, err2)
    to combine multiple errors
  • context.WithCancelCause
    :
    ctx, cancel := context.WithCancelCause(parent)
    then
    cancel(err)
  • context.Cause
    :
    context.Cause(ctx)
    to get the error that caused cancellation
  • strings.Clone
    :使用
    strings.Clone(s)
    复制字符串,避免内存共享
  • bytes.Clone
    :使用
    bytes.Clone(b)
    复制字节切片
  • strings.CutPrefix/CutSuffix
    :使用
    if rest, ok := strings.CutPrefix(s, "pre:"); ok { ... }
  • errors.Join
    :使用
    errors.Join(err1, err2)
    合并多个错误
  • context.WithCancelCause
    :使用
    ctx, cancel := context.WithCancelCause(parent)
    ,然后调用
    cancel(err)
  • context.Cause
    :使用
    context.Cause(ctx)
    获取导致取消的错误

Go 1.21+

Go 1.21+

Built-ins:
  • min
    /
    max
    :
    max(a, b)
    instead of if/else comparisons
  • clear
    :
    clear(m)
    to delete all map entries,
    clear(s)
    to zero slice elements
slices package:
  • slices.Contains
    :
    slices.Contains(items, x)
    instead of manual loops
  • slices.Index
    :
    slices.Index(items, x)
    returns index (-1 if not found)
  • slices.IndexFunc
    :
    slices.IndexFunc(items, func(item T) bool { return item.ID == id })
  • slices.SortFunc
    :
    slices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.X, b.X) })
  • slices.Sort
    :
    slices.Sort(items)
    for ordered types
  • slices.Max
    /
    slices.Min
    :
    slices.Max(items)
    instead of manual loop
  • slices.Reverse
    :
    slices.Reverse(items)
    instead of manual swap loop
  • slices.Compact
    :
    slices.Compact(items)
    removes consecutive duplicates in-place
  • slices.Clip
    :
    slices.Clip(s)
    removes unused capacity
  • slices.Clone
    :
    slices.Clone(s)
    creates a copy
maps package:
  • maps.Clone
    :
    maps.Clone(m)
    instead of manual map iteration
  • maps.Copy
    :
    maps.Copy(dst, src)
    copies entries from src to dst
  • maps.DeleteFunc
    :
    maps.DeleteFunc(m, func(k K, v V) bool { return condition })
sync package:
  • sync.OnceFunc
    :
    f := sync.OnceFunc(func() { ... })
    instead of
    sync.Once
    + wrapper
  • sync.OnceValue
    :
    getter := sync.OnceValue(func() T { return computeValue() })
context package:
  • context.AfterFunc
    :
    stop := context.AfterFunc(ctx, cleanup)
    runs cleanup on cancellation
  • context.WithTimeoutCause
    :
    ctx, cancel := context.WithTimeoutCause(parent, d, err)
  • context.WithDeadlineCause
    : Similar with deadline instead of duration
内置函数:
  • min
    /
    max
    :使用
    max(a, b)
    替代if/else比较
  • clear
    :使用
    clear(m)
    删除所有map条目,使用
    clear(s)
    将切片元素置零
slices包:
  • slices.Contains
    :使用
    slices.Contains(items, x)
    替代手动循环
  • slices.Index
    slices.Index(items, x)
    返回索引(未找到则返回-1)
  • slices.IndexFunc
    slices.IndexFunc(items, func(item T) bool { return item.ID == id })
  • slices.SortFunc
    slices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.X, b.X) })
  • slices.Sort
    slices.Sort(items)
    用于有序类型
  • slices.Max
    /
    slices.Min
    :使用
    slices.Max(items)
    替代手动循环
  • slices.Reverse
    :使用
    slices.Reverse(items)
    替代手动交换循环
  • slices.Compact
    :使用
    slices.Compact(items)
    原地移除连续重复元素
  • slices.Clip
    :使用
    slices.Clip(s)
    移除未使用的容量
  • slices.Clone
    :使用
    slices.Clone(s)
    创建副本
maps包:
  • maps.Clone
    :使用
    maps.Clone(m)
    替代手动遍历map
  • maps.Copy
    :使用
    maps.Copy(dst, src)
    将条目从src复制到dst
  • maps.DeleteFunc
    :使用
    maps.DeleteFunc(m, func(k K, v V) bool { return condition })
sync包:
  • sync.OnceFunc
    :使用
    f := sync.OnceFunc(func() { ... })
    替代
    sync.Once
    + 包装函数
  • sync.OnceValue
    :使用
    getter := sync.OnceValue(func() T { return computeValue() })
context包:
  • context.AfterFunc
    :使用
    stop := context.AfterFunc(ctx, cleanup)
    在取消时执行清理操作
  • context.WithTimeoutCause
    :使用
    ctx, cancel := context.WithTimeoutCause(parent, d, err)
  • context.WithDeadlineCause
    :类似WithTimeoutCause,但使用截止时间而非时长

Go 1.22+

Go 1.22+

Loops:
  • for i := range n
    :
    for i := range len(items)
    instead of
    for i := 0; i < len(items); i++
  • Loop variables are now safe to capture in goroutines (each iteration has its own copy)
cmp package:
  • cmp.Or
    :
    cmp.Or(flag, env, config, "default")
    returns first non-zero value
go
// Instead of:
name := os.Getenv("NAME")
if name == "" {
    name = "default"
}
// Use:
name := cmp.Or(os.Getenv("NAME"), "default")
reflect package:
  • reflect.TypeFor
    :
    reflect.TypeFor[T]()
    instead of
    reflect.TypeOf((*T)(nil)).Elem()
net/http:
  • Enhanced
    http.ServeMux
    patterns:
    mux.HandleFunc("GET /api/{id}", handler)
    with method and path params
  • r.PathValue("id")
    to get path parameters
循环:
  • for i := range n
    :使用
    for i := range len(items)
    替代
    for i := 0; i < len(items); i++
  • 循环变量现在可以安全地在goroutine中捕获(每次迭代都有自己的副本)
cmp包:
  • cmp.Or
    :使用
    cmp.Or(flag, env, config, "default")
    返回第一个非零值
go
// 替代:
name := os.Getenv("NAME")
if name == "" {
    name = "default"
}
// 使用:
name := cmp.Or(os.Getenv("NAME"), "default")
reflect包:
  • reflect.TypeFor
    :使用
    reflect.TypeFor[T]()
    替代
    reflect.TypeOf((*T)(nil)).Elem()
net/http:
  • 增强的
    http.ServeMux
    路由模式:
    mux.HandleFunc("GET /api/{id}", handler)
    支持方法和路径参数
  • 使用
    r.PathValue("id")
    获取路径参数

Go 1.23+

Go 1.23+

  • maps.Keys(m)
    /
    maps.Values(m)
    return iterators
  • slices.Collect(iter)
    not manual loop to build slice from iterator
  • slices.Sorted(iter)
    to collect and sort in one step
go
keys := slices.Collect(maps.Keys(m))       // not: for k := range m { keys = append(keys, k) }
sortedKeys := slices.Sorted(maps.Keys(m))  // collect + sort
for k := range maps.Keys(m) { process(k) } // iterate directly
  • maps.Keys(m)
    /
    maps.Values(m)
    返回迭代器
  • 使用
    slices.Collect(iter)
    替代手动循环从迭代器构建切片
  • 使用
    slices.Sorted(iter)
    一步完成收集和排序
go
keys := slices.Collect(maps.Keys(m))       // 替代:for k := range m { keys = append(keys, k) }
sortedKeys := slices.Sorted(maps.Keys(m))  // 收集+排序
for k := range maps.Keys(m) { process(k) } // 直接迭代

Go 1.24+

Go 1.24+

  • t.Context()
    not
    context.WithCancel(context.Background())
    in tests. ALWAYS use t.Context() when a test function needs a context.
Before:
go
func TestFoo(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    result := doSomething(ctx)
}
After:
go
func TestFoo(t *testing.T) {
    ctx := t.Context()
    result := doSomething(ctx)
}
  • omitzero
    not
    omitempty
    in JSON struct tags. ALWAYS use omitzero for time.Duration, time.Time, structs, slices, maps.
Before:
go
type Config struct {
    Timeout time.Duration `json:"timeout,omitempty"` // doesn't work for Duration!
}
After:
go
type Config struct {
    Timeout time.Duration `json:"timeout,omitzero"`
}
  • b.Loop()
    not
    for i := 0; i < b.N; i++
    in benchmarks. ALWAYS use b.Loop() for the main loop in benchmark functions.
Before:
go
func BenchmarkFoo(b *testing.B) {
    for i := 0; i < b.N; i++ {
        doWork()
    }
}
After:
go
func BenchmarkFoo(b *testing.B) {
    for b.Loop() {
        doWork()
    }
}
  • strings.SplitSeq
    not
    strings.Split
    when iterating. ALWAYS use SplitSeq/FieldsSeq when iterating over split results in a for-range loop.
Before:
go
for _, part := range strings.Split(s, ",") {
    process(part)
}
After:
go
for part := range strings.SplitSeq(s, ",") {
    process(part)
}
Also:
strings.FieldsSeq
,
bytes.SplitSeq
,
bytes.FieldsSeq
.
  • 在测试中使用
    t.Context()
    而非
    context.WithCancel(context.Background())
    。当测试函数需要context时,务必使用
    t.Context()
之前的写法:
go
func TestFoo(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    result := doSomething(ctx)
}
改进后的写法:
go
func TestFoo(t *testing.T) {
    ctx := t.Context()
    result := doSomething(ctx)
}
  • 在JSON结构体标签中使用
    omitzero
    而非
    omitempty
    。对于time.Duration、time.Time、结构体、切片、map,务必使用omitzero。
之前的写法:
go
type Config struct {
    Timeout time.Duration `json:"timeout,omitempty"` // 对Duration无效!
}
改进后的写法:
go
type Config struct {
    Timeout time.Duration `json:"timeout,omitzero"`
}
  • 在基准测试中使用
    b.Loop()
    而非
    for i := 0; i < b.N; i++
    。在基准测试函数的主循环中,务必使用b.Loop()。
之前的写法:
go
func BenchmarkFoo(b *testing.B) {
    for i := 0; i < b.N; i++ {
        doWork()
    }
}
改进后的写法:
go
func BenchmarkFoo(b *testing.B) {
    for b.Loop() {
        doWork()
    }
}
  • 迭代时使用
    strings.SplitSeq
    而非
    strings.Split
    。在for-range循环中迭代分割结果时,务必使用SplitSeq/FieldsSeq。
之前的写法:
go
for _, part := range strings.Split(s, ",") {
    process(part)
}
改进后的写法:
go
for part := range strings.SplitSeq(s, ",") {
    process(part)
}
同样适用的还有:
strings.FieldsSeq
bytes.SplitSeq
bytes.FieldsSeq

Go 1.25+

Go 1.25+

  • wg.Go(fn)
    not
    wg.Add(1)
    +
    go func() { defer wg.Done(); ... }()
    . ALWAYS use wg.Go() when spawning goroutines with sync.WaitGroup.
Before:
go
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func() {
        defer wg.Done()
        process(item)
    }()
}
wg.Wait()
After:
go
var wg sync.WaitGroup
for _, item := range items {
    wg.Go(func() {
        process(item)
    })
}
wg.Wait()
  • 当使用sync.WaitGroup启动goroutine时,使用
    wg.Go(fn)
    而非
    wg.Add(1)
    +
    go func() { defer wg.Done(); ... }()
    。务必使用wg.Go()。
之前的写法:
go
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func() {
        defer wg.Done()
        process(item)
    }()
}
wg.Wait()
改进后的写法:
go
var wg sync.WaitGroup
for _, item := range items {
    wg.Go(func() {
        process(item)
    })
}
wg.Wait()

Go 1.26+

Go 1.26+

  • new(val)
    not
    x := val; &x
    — returns pointer to any value. Go 1.26 extends new() to accept expressions, not just types. Type is inferred: new(0) → *int, new("s") → *string, new(T{}) → *T. DO NOT use
    x := val; &x
    pattern — always use new(val) directly. DO NOT use redundant casts like new(int(0)) — just write new(0). Common use case: struct fields with pointer types.
Before:
go
timeout := 30
debug := true
cfg := Config{
    Timeout: &timeout,
    Debug:   &debug,
}
After:
go
cfg := Config{
    Timeout: new(30),   // *int
    Debug:   new(true), // *bool
}
  • errors.AsType[T](err)
    not
    errors.As(err, &target)
    . ALWAYS use errors.AsType when checking if error matches a specific type.
Before:
go
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    handle(pathErr)
}
After:
go
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
    handle(pathErr)
}
  • 使用
    new(val)
    而非
    x := val; &x
    ——返回任意值的指针。Go 1.26扩展了new()的功能,使其可以接受表达式,而不仅仅是类型。类型会自动推断:new(0) → *int,new("s") → *string,new(T{}) → *T。请勿使用
    x := val; &x
    模式——务必直接使用new(val)。请勿使用多余的类型转换如new(int(0))——直接写new(0)即可。常见使用场景:结构体中的指针类型字段。
之前的写法:
go
timeout := 30
debug := true
cfg := Config{
    Timeout: &timeout,
    Debug:   &debug,
}
改进后的写法:
go
cfg := Config{
    Timeout: new(30),   // *int
    Debug:   new(true), // *bool
}
  • 使用
    errors.AsType[T](err)
    而非
    errors.As(err, &target)
    。当检查错误是否匹配特定类型时,务必使用errors.AsType。
之前的写法:
go
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    handle(pathErr)
}
改进后的写法:
go
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
    handle(pathErr)
}