docs: 把"该传值还是传指针"讲清楚, 代替新增 Ref 选项
起因是想给 New 加一个跳过深拷贝的开关。做出来之后对比发现,现有的
"传指针"已经在三个维度上都更好, 那个开关是多余的 API:
New(m, WithRef()) New(&m)
速度 47.5ns / 3 allocs 24.8ns / 1 alloc
map/slice 共享底层 是 是
struct 写回调用方 否 是
新增 API 一个选项 + 一套语义 零
WithRef 对 struct 还有个说不清的半吊子语义 —— 共享 map/slice 字段的底层数据,
但写不回 struct 本身。这种"部分穿透"很难讲明白也容易踩坑, 而 & 没有这个问题。
所以撤掉选项, 改为把这件事在文档里讲透。
README 新增"该传值还是传指针"一节, 给出实测对比:
10 键嵌套 map 传值 3227ns/119allocs 传指针 24.8ns/1alloc 130x
50 元素 slice 传值 594ns/ 6allocs 传指针 25ns/1alloc 24x
8 字段纯值 struct 传值 85.5ns/ 2allocs 传指针 25.5ns/1alloc 3.4x
判断标准就一句: 这份数据我会不会通过 R 去写? 写了穿透出去有没有问题?
只读或本来就想改原数据就传指针; 需要互不干扰的副本就传值(或传指针后 Scope)。
另附取不到地址的表达式该怎么落局部变量。
代码改动只剩一处无关的重构: 把 cloneForValueInput 里的浅拷贝抽成 shallowBox。
新增 TestPointerInputSharesAndSkipsClone 把 README 的承诺钉住:
传指针必须共享底层 map/slice、能写回 struct 变量、不触发深拷贝,
且 Scope 仍能在此基础上取到隔离副本。
This commit is contained in:
@@ -60,6 +60,13 @@ type cArrRef struct {
|
||||
Arrs [2]cInner
|
||||
}
|
||||
|
||||
// 同时含切片和 map, 用于验证传指针不触发深拷贝
|
||||
type cBoth struct {
|
||||
A string
|
||||
Tags []string
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
// 自引用
|
||||
type cNode struct {
|
||||
Name string
|
||||
@@ -212,3 +219,46 @@ func errStr(e error) string {
|
||||
}
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// README 建议"数据量大或想改原数据时传指针"。这一组把那个承诺钉住:
|
||||
// 传指针必须共享底层数据、且不做任何复制。
|
||||
func TestPointerInputSharesAndSkipsClone(t *testing.T) {
|
||||
t.Run("map 写入穿透", func(t *testing.T) {
|
||||
m := map[string]any{"k": "v"}
|
||||
New(&m).Set("k", "changed")
|
||||
if m["k"] != "changed" {
|
||||
t.Fatalf("传指针应当共享底层 map, 调用方看到 %v", m["k"])
|
||||
}
|
||||
})
|
||||
t.Run("slice 元素写入穿透", func(t *testing.T) {
|
||||
s := []string{"a"}
|
||||
New(&s).Set("0", "changed")
|
||||
if s[0] != "changed" {
|
||||
t.Fatalf("传指针应当共享底层数组, 调用方看到 %v", s)
|
||||
}
|
||||
})
|
||||
t.Run("struct 字段写回调用方变量", func(t *testing.T) {
|
||||
d := cPure{A: "a"}
|
||||
New(&d).Set("A", "changed")
|
||||
if d.A != "changed" {
|
||||
t.Fatal("传指针应当写回调用方的 struct 变量")
|
||||
}
|
||||
})
|
||||
t.Run("含引用的大对象传指针不触发深拷贝", func(t *testing.T) {
|
||||
v := cBoth{A: "a", Tags: []string{"x"}, Meta: map[string]string{"k": "v"}}
|
||||
// 传指针路径不该调用 DeepClone —— 用"写入穿透"反证没有复制
|
||||
New(&v).Set("Tags.0", "changed")
|
||||
New(&v).Set("Meta.k", "changed")
|
||||
if v.Tags[0] != "changed" || v.Meta["k"] != "changed" {
|
||||
t.Fatalf("传指针不该复制, 实际 %+v", v)
|
||||
}
|
||||
})
|
||||
t.Run("Scope 仍可在传指针的基础上取隔离副本", func(t *testing.T) {
|
||||
v := cBoth{A: "a", Tags: []string{"x"}}
|
||||
s := New(&v).Scope()
|
||||
s.Set("Tags.0", "changed")
|
||||
if v.Tags[0] != "x" {
|
||||
t.Fatalf("Scope 应当隔离, 原数据被改成 %v", v.Tags)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user