use-modern-go
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseModern 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 unknownHow 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) over legacy patternscmp - 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.Sinceinstead oftime.Since(start)time.Now().Sub(start)
- :使用
time.Since替代time.Since(start)time.Now().Sub(start)
Go 1.8+
Go 1.8+
- :
time.Untilinstead oftime.Until(deadline)deadline.Sub(time.Now())
- :使用
time.Until替代time.Until(deadline)deadline.Sub(time.Now())
Go 1.13+
Go 1.13+
- :
errors.Isinstead oferrors.Is(err, target)(works with wrapped errors)err == target
- :使用
errors.Is替代errors.Is(err, target)(支持包装后的错误)err == target
Go 1.18+
Go 1.18+
- : Use
anyinstead ofanyinterface{} - :
bytes.Cutinstead of Index+slicebefore, after, found := bytes.Cut(b, sep) - :
strings.Cutbefore, after, found := strings.Cut(s, sep)
- :使用
any替代anyinterface{} - :使用
bytes.Cut替代索引+切片的方式before, after, found := bytes.Cut(b, sep) - :使用
strings.Cutbefore, after, found := strings.Cut(s, sep)
Go 1.19+
Go 1.19+
- :
fmt.Appendfinstead ofbuf = fmt.Appendf(buf, "x=%d", x)[]byte(fmt.Sprintf(...)) - /
atomic.Bool/atomic.Int64: Type-safe atomics instead ofatomic.Pointer[T]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.Cloneto copy string without sharing memorystrings.Clone(s) - :
bytes.Cloneto copy byte slicebytes.Clone(b) - :
strings.CutPrefix/CutSuffixif rest, ok := strings.CutPrefix(s, "pre:"); ok { ... } - :
errors.Jointo combine multiple errorserrors.Join(err1, err2) - :
context.WithCancelCausethenctx, cancel := context.WithCancelCause(parent)cancel(err) - :
context.Causeto get the error that caused cancellationcontext.Cause(ctx)
- :使用
strings.Clone复制字符串,避免内存共享strings.Clone(s) - :使用
bytes.Clone复制字节切片bytes.Clone(b) - :使用
strings.CutPrefix/CutSuffixif 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:maxinstead of if/else comparisonsmax(a, b) - :
clearto delete all map entries,clear(m)to zero slice elementsclear(s)
slices package:
- :
slices.Containsinstead of manual loopsslices.Contains(items, x) - :
slices.Indexreturns index (-1 if not found)slices.Index(items, x) - :
slices.IndexFuncslices.IndexFunc(items, func(item T) bool { return item.ID == id }) - :
slices.SortFuncslices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.X, b.X) }) - :
slices.Sortfor ordered typesslices.Sort(items) - /
slices.Max:slices.Mininstead of manual loopslices.Max(items) - :
slices.Reverseinstead of manual swap loopslices.Reverse(items) - :
slices.Compactremoves consecutive duplicates in-placeslices.Compact(items) - :
slices.Clipremoves unused capacityslices.Clip(s) - :
slices.Clonecreates a copyslices.Clone(s)
maps package:
- :
maps.Cloneinstead of manual map iterationmaps.Clone(m) - :
maps.Copycopies entries from src to dstmaps.Copy(dst, src) - :
maps.DeleteFuncmaps.DeleteFunc(m, func(k K, v V) bool { return condition })
sync package:
- :
sync.OnceFuncinstead off := sync.OnceFunc(func() { ... })+ wrappersync.Once - :
sync.OnceValuegetter := sync.OnceValue(func() T { return computeValue() })
context package:
- :
context.AfterFuncruns cleanup on cancellationstop := context.AfterFunc(ctx, cleanup) - :
context.WithTimeoutCausectx, cancel := context.WithTimeoutCause(parent, d, err) - : Similar with deadline instead of duration
context.WithDeadlineCause
内置函数:
- /
min:使用max替代if/else比较max(a, b) - :使用
clear删除所有map条目,使用clear(m)将切片元素置零clear(s)
slices包:
- :使用
slices.Contains替代手动循环slices.Contains(items, x) - :
slices.Index返回索引(未找到则返回-1)slices.Index(items, x) - :
slices.IndexFuncslices.IndexFunc(items, func(item T) bool { return item.ID == id }) - :
slices.SortFuncslices.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替代手动遍历mapmaps.Clone(m) - :使用
maps.Copy将条目从src复制到dstmaps.Copy(dst, src) - :使用
maps.DeleteFuncmaps.DeleteFunc(m, func(k K, v V) bool { return condition })
sync包:
- :使用
sync.OnceFunc替代f := sync.OnceFunc(func() { ... })+ 包装函数sync.Once - :使用
sync.OnceValuegetter := sync.OnceValue(func() T { return computeValue() })
context包:
- :使用
context.AfterFunc在取消时执行清理操作stop := context.AfterFunc(ctx, cleanup) - :使用
context.WithTimeoutCausectx, cancel := context.WithTimeoutCause(parent, d, err) - :类似WithTimeoutCause,但使用截止时间而非时长
context.WithDeadlineCause
Go 1.22+
Go 1.22+
Loops:
- :
for i := range ninstead offor i := range len(items)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.Orreturns first non-zero valuecmp.Or(flag, env, config, "default")
go
// Instead of:
name := os.Getenv("NAME")
if name == "" {
name = "default"
}
// Use:
name := cmp.Or(os.Getenv("NAME"), "default")reflect package:
- :
reflect.TypeForinstead ofreflect.TypeFor[T]()reflect.TypeOf((*T)(nil)).Elem()
net/http:
- Enhanced patterns:
http.ServeMuxwith method and path paramsmux.HandleFunc("GET /api/{id}", handler) - to get path parameters
r.PathValue("id")
循环:
- :使用
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)return iteratorsmaps.Values(m) - not manual loop to build slice from iterator
slices.Collect(iter) - to collect and sort in one step
slices.Sorted(iter)
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+
- not
t.Context()in tests. ALWAYS use t.Context() when a test function needs a context.context.WithCancel(context.Background())
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)
}- not
omitzeroin JSON struct tags. ALWAYS use omitzero for time.Duration, time.Time, structs, slices, maps.omitempty
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"`
}- not
b.Loop()in benchmarks. ALWAYS use b.Loop() for the main loop in benchmark functions.for i := 0; i < b.N; i++
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()
}
}- not
strings.SplitSeqwhen iterating. ALWAYS use SplitSeq/FieldsSeq when iterating over split results in a for-range loop.strings.Split
Before:
go
for _, part := range strings.Split(s, ",") {
process(part)
}After:
go
for part := range strings.SplitSeq(s, ",") {
process(part)
}Also: , , .
strings.FieldsSeqbytes.SplitSeqbytes.FieldsSeq- 在测试中使用而非
t.Context()。当测试函数需要context时,务必使用context.WithCancel(context.Background())。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。对于time.Duration、time.Time、结构体、切片、map,务必使用omitzero。omitempty
之前的写法:
go
type Config struct {
Timeout time.Duration `json:"timeout,omitempty"` // 对Duration无效!
}改进后的写法:
go
type Config struct {
Timeout time.Duration `json:"timeout,omitzero"`
}- 在基准测试中使用而非
b.Loop()。在基准测试函数的主循环中,务必使用b.Loop()。for i := 0; i < b.N; i++
之前的写法:
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。在for-range循环中迭代分割结果时,务必使用SplitSeq/FieldsSeq。strings.Split
之前的写法:
go
for _, part := range strings.Split(s, ",") {
process(part)
}改进后的写法:
go
for part := range strings.SplitSeq(s, ",") {
process(part)
}同样适用的还有:、、。
strings.FieldsSeqbytes.SplitSeqbytes.FieldsSeqGo 1.25+
Go 1.25+
- not
wg.Go(fn)+wg.Add(1). ALWAYS use wg.Go() when spawning goroutines with sync.WaitGroup.go func() { defer wg.Done(); ... }()
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)。务必使用wg.Go()。go func() { defer wg.Done(); ... }()
之前的写法:
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+
- not
new(val)— 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 usex := val; &xpattern — 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.x := val; &x
Before:
go
timeout := 30
debug := true
cfg := Config{
Timeout: &timeout,
Debug: &debug,
}After:
go
cfg := Config{
Timeout: new(30), // *int
Debug: new(true), // *bool
}- not
errors.AsType[T](err). ALWAYS use errors.AsType when checking if error matches a specific type.errors.As(err, &target)
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)——返回任意值的指针。Go 1.26扩展了new()的功能,使其可以接受表达式,而不仅仅是类型。类型会自动推断:new(0) → *int,new("s") → *string,new(T{}) → *T。请勿使用x := val; &x模式——务必直接使用new(val)。请勿使用多余的类型转换如new(int(0))——直接写new(0)即可。常见使用场景:结构体中的指针类型字段。x := val; &x
之前的写法:
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.AsType。errors.As(err, &target)
之前的写法:
go
var pathErr *os.PathError
if errors.As(err, &pathErr) {
handle(pathErr)
}改进后的写法:
go
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
handle(pathErr)
}