package reflux // 链式 Get 传的是地址而不是数据副本 —— 这一点决定了每次 Get 那 1 次分配 // 只是 24 字节的游标, 与被访问数据的大小无关。 import "testing" // 链式 Get 到底是"传地址"还是"每一跳复制一份数据"? // // 结论: 传地址。每次 Get 在堆上新建的只是一个 24 字节的游标(类型描述符指针 + // 目标地址 + 可写标记), 它的 ptr 字段直接指向原对象内部, 被访问的数据一个字节 // 都没有被复制。 func TestChainedGetSharesAddress(t *testing.T) { o := newUxOuter() r := New(o) // 原始字段的真实地址 want := &o.Mid.Inner.City oneShot := r.Get("Mid", "Inner", "City").Ptr().(*string) dotted := r.Get("Mid.Inner.City").Ptr().(*string) chained := r.Get("Mid").Get("Inner").Get("City").Ptr().(*string) if oneShot != want { t.Fatalf("一次调用拿到的不是原字段地址: %p vs %p", oneShot, want) } if dotted != want { t.Fatalf("点号路径拿到的不是原字段地址: %p vs %p", dotted, want) } if chained != want { t.Fatalf("链式调用拿到的不是原字段地址: %p vs %p", chained, want) } // 中间跳也是原对象内部的地址, 不是副本 mid := r.Get("Mid").Ptr().(*uxMiddle) if mid != &o.Mid { t.Fatalf("中间跳 Mid 不是原地址: %p vs %p", mid, &o.Mid) } uxInner := r.Get("Mid").Get("Inner").Ptr().(*uxInner) if uxInner != &o.Mid.Inner { t.Fatalf("中间跳 Inner 不是原地址: %p vs %p", uxInner, &o.Mid.Inner) } // 经链式写入, 必须落到原对象上 r.Get("Mid").Get("Inner").Set("City", "WrittenViaChain") if o.Mid.Inner.City != "WrittenViaChain" { t.Fatalf("链式写入没落到原对象: %q", o.Mid.Inner.City) } } // reflux 现有实现同样是地址传递(reflect.Value 内部存的也是字段地址), // 两边在这一点上语义一致。 func TestRefluxAlsoSharesAddress(t *testing.T) { o := newUxOuter() r := newLegacy(o) want := &o.Mid.Inner.City got := r.Get("Mid").Get("Inner").Get("City").Ptr().(*string) if got != want { t.Fatalf("reflux 链式拿到的不是原字段地址: %p vs %p", got, want) } } // 唯一会复制的两种情况: map 取值 和 interface 拆出非指针值。 // 它们在 Go 里本来就不可寻址, reflect 也一样只能给副本。 func TestMapAndIfaceAreCopies(t *testing.T) { o := newUxOuter() r := New(o) // map: 两次取同一个键, 拿到两个不同的地址 => 是副本 a := r.Get("Objs", "o1").Ptr().(*uxInner) b := r.Get("Objs", "o1").Ptr().(*uxInner) if a == b { t.Fatal("map 取值应该每次给一份独立副本") } if a.City != "Chengdu" || b.City != "Chengdu" { t.Fatalf("副本内容不对: %q %q", a.City, b.City) } // interface 里装的是非指针值, 同样是副本 c := r.Get("Mid", "Any").Ptr().(*uxInner) d := r.Get("Mid", "Any").Ptr().(*uxInner) if c == d { t.Fatal("interface 拆非指针值应该每次给一份独立副本") } // 但 slice 元素是原地址(底层数组), 不是副本 e := r.Get("Structs", "0").Ptr().(*uxInner) if e != &o.Structs[0] { t.Fatalf("slice 元素应该是原地址: %p vs %p", e, &o.Structs[0]) } }