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:
@@ -97,6 +97,35 @@ rfx.Set("Name", "Bob")
|
||||
// rfx 内部是独立的深度克隆
|
||||
```
|
||||
|
||||
#### 该传值还是传指针
|
||||
|
||||
**默认传值是安全的,但深度克隆的代价随数据规模增长**:
|
||||
|
||||
| 输入 | `New(v)` 传值 | `New(&v)` 传指针 | 差距 |
|
||||
|---|---|---|---:|
|
||||
| 10 键嵌套 map | 3227 ns / 119 allocs | 24.8 ns / 1 alloc | **130x** |
|
||||
| 50 元素 slice | 594 ns / 6 allocs | 25 ns / 1 alloc | **24x** |
|
||||
| 8 字段纯值 struct | 85.5 ns / 2 allocs | 25.5 ns / 1 alloc | 3.4x |
|
||||
|
||||
判断标准就一句:**这份数据我会不会通过 R 去写?写了穿透出去有没有问题?**
|
||||
|
||||
- 只读,或者本来就想改调用方的数据 → **传指针**
|
||||
- 需要一份互不干扰的副本 → 传值(或者传指针之后用 `Scope()` 取局部副本)
|
||||
|
||||
取不到地址的表达式,先落一个局部变量:
|
||||
|
||||
```go
|
||||
// 取不到地址
|
||||
return reflux.New(lo.Assign(a, b))
|
||||
|
||||
// 落一个变量, 顺带让"共享数据"这件事在调用点就看得见
|
||||
m := lo.Assign(a, b)
|
||||
return reflux.New(&m)
|
||||
```
|
||||
|
||||
纯值类型(不含指针/slice/map/interface)的传值成本已经优化过 —— 逐字节复制
|
||||
就已经完全独立,不走深拷贝的递归。所以上表里 struct 那一行差距最小。
|
||||
|
||||
#### Interface 类型支持
|
||||
|
||||
Reflux 支持 `interface{}` (或 `any`) 类型,会自动解析到实际类型:
|
||||
|
||||
Reference in New Issue
Block a user