移动实战 — 控制流、下标、Copy 与共享所有权
移动实战 — 控制流、下标、Copy 与共享所有权
这一章回答四个「那具体怎么办」: 分支和循环里怎么移动?怎么从一个向量里取走一个元素? 整数凭什么不用移动那套规矩?需要一个值有多个主人时找谁? 上一章给了所有权和移动的定义,这一章全是边界上的实战。
1. 顶层全景:规则的三处松动
所有权树太死 → 三处松动:
① 移动(本章前部):把所有权从一处过户到另一处——建/拆/重组树的唯一工具
② Copy 类型(本章中部):简单类型免除移动,按位复制
③ Rc/Arc(本章后部):受限的多主人,引用计数兜底
(还有第四处「借用」,整章留给下一章)图说:松动都有代价和限制,没有一处是白送的。
2. 移动与控制流
原则一句话:只要一个变量「有可能被搬走、且之后没Definitely拿到新值」,它就被视为未初始化1。 落到具体控制流:
分支:条件求值后变量还有值,两个分支就都能搬走它——反正只走一条路:
let x = vec![10, 20, 30];
if c {
f(x); // 这里搬走,ok
} else {
g(x); // 这里也搬走,ok
}
h(x); // 错误:某条路径上 x 已被搬走循环:循环里搬走是禁止的——第一轮搬走了,第二轮它就是未初始化。除非每轮都Definitely补个新值:
let mut x = vec![10, 20, 30];
while f() {
g(x); // 搬走
x = h(); // 下一轮之前Definitely补新值——允许
}这套「definite assignment(确定赋值)」式的分析不新鲜,Java 也有类似检查; Rust 选的是简单一侧:流敏感分析不猜循环条件的真假,假设任何条件都可能真也可能假2。
3. 主线例子:怎么从向量里取走一个元素
这是本章的主线例子。向量 v 里有五个字符串 "101" 到 "105",你想把 "103" 拿走:
let third = v[2]; // 错误:cannot move out of index of `Vec<String>`为什么不行? 移动会让源变「未初始化」。若允许 v[2] 搬走,向量就得记住
「第 3 个格子现在是空的」——最一般的情形下,每个向量都得随身带一份
「哪些元素活着」的位图。书的原话:向量就该是向量,不是别的3。
编译器的报错信息直接给了出路(「consider borrowing here: &v[2]」)——多数时候你要的只是看,
那就借。真要走所有权,有三种正路,各留不同的向量状态:
// 1. 弹尾巴:拿走最后一个,向量变短
let fifth = v.pop().expect("vector empty!"); // 得 "105"
// 2. swap_remove:拿走指定下标的,把最后一个填进它的位置(乱序但快)
let second = v.swap_remove(1); // 得 "102"
// 3. replace:塞个替补进去,拿走原来的
let third = std::mem::replace(&mut v[2], "substitute".to_string()); // 得 "103"
// 此时 v == ["101", "104", "substitute"] ——始终满员,只是可能变短三招的共同点:取走后向量保持「完全填充」的状态——所有权系统永远不用记哪个格子空了4。
更顺手的版本:把字段类型改成 Option<String>。
None 本来就是合法值,「取走」就变成「换成 None」:
let first_name = composers[0].name.take(); // 拿走 Some("Palestrina"),原地留 NoneOption::take() 就是 replace(&mut x, None) 的专名——这个手法常用到值得一个专属方法5。
还有一招:整个向量给循环。 for mut s in v 把向量所有权交给循环机制,
逐元素搬进 s,循环体里可以改它再打印;向量中途的任何「半空状态」对外不可见,
因为向量已经不在你手里了6。
三招的完整代码与结果(原书逐行给了断言)7:
let mut v = Vec::new();
for i in 101..106 { v.push(i.to_string()); }
let fifth = v.pop().expect("vector empty!"); // 1: 弹尾
assert_eq!(fifth, "105");
let second = v.swap_remove(1); // 2: 尾填洞
assert_eq!(second, "102");
let third = std::mem::replace(&mut v[2], "substitute".to_string()); // 3: 塞替补
assert_eq!(third, "103");
assert_eq!(v, vec!["101", "104", "substitute"]); // 向量始终满员对照下三种取法的取舍:pop 只碰尾巴,O(1);swap_remove 快,但破坏了元素顺序 (最后一个元素搬进了被挖的洞);replace 保序,但你要先造一个替补值。 没有全能选项,因为「快速取走 + 保持满员 + 保序」在向量身上不可兼得—— 这正是「所有权系统不记空位」这条决定的直接后果8。
整个向量交给 for:消耗式遍历
书上的「自由、平等、博爱」例子把消耗式遍历演得最完整9:
let v = vec![
"liberté".to_string(),
"égalité".to_string(),
"fraternité".to_string(),
];
for mut s in v { // 向量本身被移进循环机制
s.push('!'); // 每个 String 归 s 所有,可以改
println!("{s}"); // 用完即弃
}for … in v 把向量所有权交给循环的内部机制,逐元素搬进 s。
因为向量已经不在你手里,没有任何代码能观察到它「半空」的中间态——
「取走一个、剩两个」这种不完整状态被所有权挡在视野之外,这正是
「不记空位」规定的另一面10。
4. Copy 类型:移动的豁免名单
移动的规矩对整数这类值是纯粹的负担。对比:
let num1: i32 = 36;
let num2 = num1; // i32 没有堆资源,没有任何东西需要「唯一主人」
println!("{num1}"); // 完全没问题i32 就是内存里的一组比特,不拥有堆资源;把比特拷过去,源和目标就彻底独立了, 「源作废」这条规矩在这里防不了任何危险,只添麻烦11。
于是有了 Copy 类型:赋值即按位复制,源保持可用。名单上的是:
全部机器整数和浮点、char、bool,以及由 Copy 类型组成的元组和定长数组12。
反向判据(经验法则):任何「值被丢弃时需要做点特别的事」的类型都不能 Copy—— Vec 要释放元素、File 要关句柄、MutexGuard 要解锁。按位复制会让「谁负责原来的资源」说不清13。 Box 也不是 Copy:它拥有堆上的被指物14。
自定义类型默认不是 Copy。 哪怕 struct Label { number: u32 } 这么纯粹,传参也会搬走——
编译器错得很难看(borrow of moved value)。要豁免,自己声明:
#[derive(Copy, Clone)]
struct Label { number: u32 }前提是所有字段都 Copy;有 String 字段就报错。为什么不自动给所有够格的类型上 Copy? 因为 Copy 是实现者的重大承诺:改成非 Copy 是破坏性的,用它的代码大量要改。 所以默认保守,加入自愿15。
和 C++ 的分野:C++ 允许你重载赋值运算符、定义拷贝/移动构造函数——
于是 a = b 可能触发任意行为。Rust 不允许:每个移动都是逐字节的浅拷贝加源作废,
每个拷贝也一样,只是源保留。作者的立论原则:代价必须对程序员可见——
基本操作必须简单,昂贵的操作(如 clone)必须显式16。
内存里发生了什么:一次对照
原书把 String 和 i32 的赋值并排画在一张图里(图 3-11),值得用语言重放17:
let string1 = "somnambulance".to_string();
let string2 = string1; // 移动:string1 的三个机器字进 string2,string1 作废
let num1: i32 = 36;
let num2 = num1; // 复制:36 这份比特进 num2,num1 原样可用字符串那次:堆里那 13 个字母原地没动,动的只是栈上的指针、容量、长度; 源变量从此「不算数」,因为不能有两个主人都以为自己该释放那块缓冲。 整数那次:36 就是全部,拷贝一份就是两个完全独立的 36,源变量作废防不住任何危险—— 所以 Rust 干脆不许它作废,这就是 Copy 存在的全部理由18。
自定义类型的两次报错
默认非 Copy 这件事,新手会连撞两次墙。第一次,传参后想用:
struct Label { number: u32 }
let l = Label { number: 3 };
print(l); // l 被搬进 print,在里面被丢弃
println!("{}", l.number); // 错误:borrow of moved value: `l`Label 不过是个 u32 的包装,被搬走纯属误伤。第二次,试图 derive 救场但字段不够格:
#[derive(Copy, Clone)]
struct StringLabel { name: String } // 错误:the trait `Copy` cannot be implemented报错指名道姓:这个字段不是 Copy。两次墙,一次教你「默认是移动」,一次教你 「Copy 是资格,不是语法」19。
5. Rc 与 Arc:受限的共享所有权
单一主人覆盖不了所有场景:有时你就是希望值「用到它的所有人都用完了再死」。
这就是引用计数指针的用途。Rust 的两个版本:Rc<T> 和 Arc<T>,
唯一区别是 Arc 用原子操作更新计数,可跨线程;Rc 更快但禁止跨线程——
不需要跨线程就别付 Arc 的开销20。
let s: Rc<String> = Rc::new("shirataki".to_string());
let t = s.clone(); // 不复制 String!只多一个指针,计数 +1
let u = s.clone(); // 计数 +1,共 3Rc::new 在堆上分配值并附一个计数。克隆 Rc 不复制 T——只加计数。
三个 Rc 指向同一块内存(计数 + 值);平常的移动规则管的是 Rc 指针本身;
最后一个 Rc 死了,值跟着死21。String 的方法可以隔着 Rc 直接用(s.contains(...))。
代价:Rc 的值不可变。 想 s.push_str(" noodles")?编译器拒绝:cannot borrow data in an Rc as mutable22。
理由先记下结论:Rust 的内存与线程安全,依赖「没有任何值同时被共享又被修改」这一铁律——
Rc 的值按定义可能被共享,所以必须不可变。为什么这条铁律成立,是下一章的事23。
坑:循环引用会泄漏。 两个 Rc 互相指着,两边的计数都降不到零,两块内存永远不放——
引用计数的经典死结,Rust 也不例外24。但有个天然的缓冲:
造环必须经过「让旧值改指新值」,而 Rc 的值不可变,所以正常手段造不出环;
能造环的突破口是「内部可变性」(还没讲的特性),用 Rc 配它才可能循环泄漏。
部分链接改用弱指针 Weak 也能拆环——书里说本书不展开,查标准库文档25。
Rc 的完整现场:从创建到最后一个死
走一遍书上的完整例子26:
use std::rc::Rc;
let s: Rc<String> = Rc::new("shirataki".to_string()); // 堆上:String + 计数 1
let t = s.clone(); // 不复制 String!计数 2
let u = s.clone(); // 计数 3此刻内存里只有一份 String 和一个计数,三个 Rc 指针都指着它。 String 的方法照常隔着 Rc 用,仿佛指针不存在:
assert!(s.contains("shira"));
assert_eq!(t.find("taki"), Some(5));
println!("{u} are quite chewy, almost bouncy, but lack flavor");平常的移动规则管的是 Rc 指针本身:把 u 传给函数,函数得到一个指针、计数加一, 函数返回,指针死、计数减一。当最后一个 Rc 死了,计数归零,String 跟着死—— 释放时机和单一主人一样确定,只是「最后一个人」由计数说了算27。
Rc 和 Arc 的唯一区别在计数的更新方式:Rc 用普通加减(快,但两个线程同时加减会算错), Arc 用原子操作(慢一点,线程安全)。所以跨线程共享必须 Arc,Rust 会在编译期 拦住把 Rc 塞进线程的尝试——不是运行时才炸28。
循环泄漏的形状与拆法
引用计数的老问题在 Rust 里形状不变:两个 Rc 互相指(书里配了图:每个对象的计数 都被对方托在 1 以上),程序永远等不到「最后一个」——内存泄漏29。
Rust 的缓冲是结构性的:Rc 的值不可变,而造环必须让旧值改指新值,
旧值不可变 ⇒ 正常手段造不出环。漏洞只在「内部可变性」(还没讲的特性)身上;
真造出来了,书上给的拆法是把环上的一条边换成弱指针 Weak——
弱指针指着值但不托计数,环就断了。Weak 的细节本书不展开30。
与 C++ 的最后一对照:不能定制移动
C++ 里赋值是可以「做手脚」的:重载赋值运算符、写拷贝/移动构造函数,
于是 a = b 背后可能触发任意复杂的逻辑。Rust 把这扇门焊死了:
移动就是逐字节浅拷贝 + 源作废,每个类型、每次移动都一样,没有特例31。
代价和收益原书掂得很清楚。代价:有些 C++ 里很顺手的接口 Rust 给不了—— 普通-looking 的代码隐式调计数、推迟昂贵复制,这类戏法没地方演。 收益:基本操作的代价变得可读。赋值就是搬三个机器字,传参就是过户, 深复制必须喊出 clone 的名字——「代价必须对程序员可见」这条原则,移动语义是它的最大应用9。
这也是全书第一次出现的「Rust 式取舍」:不是这个好不好,而是可预测性和表达力选哪个。 移动语义、无隐式转换、无重载,都是同一枚硬币。
6. 作者的判断与证据
- **「基本操作必须简单、昂贵操作必须显式」**是原书明写的原则(costs should be apparent), 移动语义、无隐式转换、无重载赋值,全是这一条原则的产物16。
- **「Rc 的坑罕见」**是作者的经验之谈:「这样的情况很少见」——无数据支撑,但与 「不可变 + 需内部可变性才能成环」的机制自洽24。
- 「对 C/C++ 老手,这笔交易不用想(no-brainer)」——作者立场,不是普适结论; 从 GC 语言过来的人适应成本不同32。
7. 边界与局限
- 内部可变性(Cell/RefCell/Mutex)是 Rc 故事的另一半,本书还没放出,循环泄漏的完整图景缺一角;
- Weak 指针只有一段指路;
take/replace/swap_remove之外的集合取元素姿势(如HashMap::remove)本章未列;- 计数指针的性能开销(原子操作在多核上的代价)只有一句「更快/更慢」的对比,无数值。
8. 可带走的
主线例子一行版: v[2] 不能直接搬走 → 要么 &v[2] 借,要么 pop/swap_remove/replace 取,
要么字段改 Option 用 take();目标都是让向量保持满员。
- 移动的控制流判据:变量可能已被搬走且没拿到新值 = 未初始化;分支两路各搬一次可以,循环里搬必须每轮换新;
- 不能
v[i]取走元素,因为向量不会记「哪个格子空了」;三种取法:pop(弹尾)、swap_remove(尾填洞)、replace(塞替补); Option::take()= replace(x, None),「取走留洞」的标准写法;- Copy 类型:按位复制、源保留;整数/浮点/char/bool 及 Copy 的组合;
- 需要特殊清理的类型(Vec/File/MutexGuard/Box)绝不能 Copy——「丢弃时要做事」是反向判据;
- 自定义类型默认移动;
#[derive(Copy, Clone)]自愿加入,且所有字段都得 Copy; - Rust 禁止重载赋值:移动=浅拷贝+源作废,行为永远可预测;
- Rc/Arc 克隆只加计数不拷数据;最后一只指针死了值才死;Arc 原子计数、可跨线程;
- Rc 的值不可变——「不许同时共享又可变」这条铁律的第一块砖;
- 循环引用会泄漏,但正常造不出环;要造环得配内部可变性,拆环可用 Weak。
9. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 控制流与移动的一般原则 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:265(搜「definitely been given a new value」) |
| 循环中移动的限制 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:279(搜「first iteration」) |
| 不能从下标搬走 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:306(搜「uninitialized」) · text/13-ch03-chapter-3-ownership-and-moves.txt:308(搜「cannot move out of index」) |
| pop / swap_remove / replace | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:325(搜「Pop a value」) · text/13-ch03-chapter-3-ownership-and-moves.txt:331(搜「swap_remove」) · text/13-ch03-chapter-3-ownership-and-moves.txt:335(搜「replace」) |
| 整个向量给循环 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:356(搜「dissects」) |
| take() 手法 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:378(搜「take method」) |
| Copy 的定义与名单 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:404(搜「Copy type」) · text/13-ch03-chapter-3-ownership-and-moves.txt:406(搜「machine integer」) |
| 「丢弃时要做事 ⇒ 非 Copy」 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:410(搜「rule of thumb」) |
| derive(Copy, Clone) | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:437(搜「derive」) |
| 实现者的承诺 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:456(搜「serious commitment」) |
| 代价可见原则 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:460(搜「apparent to the programmer」) |
| Rc/Arc 分工 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:468(搜「atomic」) |
| 克隆只加计数 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:479(搜「does not copy」) |
| Rc 值不可变 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:491(搜「immutable」) · text/13-ch03-chapter-3-ownership-and-moves.txt:497(搜「cannot borrow data」) |
| 循环引用泄漏 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:505(搜「never be freed」) |
| 三招取元素的完整代码 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:325(搜「Pop a value」) |
| String 与 i32 的内存对照 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:400(搜「pattern of bits」) |
| Rc 的完整现场 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:475(搜「shirataki」) |
| 循环泄漏与 Weak | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:505(搜「never be freed」) |
| 不能定制移动 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:458(搜「byte-for-byte」) |
| Weak 指针 | Chapter 3. Ownership and Moves | text/13-ch03-chapter-3-ownership-and-moves.txt:511(搜「weak pointers」) |
Footnotes
-
出处:「Chapter 3. Ownership and Moves」第 265 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:265,搜「definitely been given a new value」)。 ↩ -
补充(不在本节原文):流程敏感分析取简单一侧的原则,见「Chapter 5. Expressions」第 695 段(
text/15-ch05-chapter-5-expressions.txt:695,搜「simplicity」)。Java 的类似分析见同段(「definite assignment」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 306 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:306,搜「extra information」)。原文:「那显然不是一种系统编程语言该有的行为;向量就该只是向量。」 ↩ -
出处:「Chapter 3. Ownership and Moves」第 341 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:341,搜「fully populated」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 378 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:378,搜「take method」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 351–356 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:351-356,搜「dissects」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 319–341 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:319-341,搜「Pop a value」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 341 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:341,搜「fully populated」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 460 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:460,搜「apparent to the programmer」)。 ↩ ↩2 -
出处:「Chapter 3. Ownership and Moves」第 356 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:356,搜「partially emptied」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 393–402 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:393-402,搜「doesn’t own any heap resources」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 404–406 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:404-406,搜「Copy types include」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 410 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:410,搜「rule of thumb」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 408 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:408,搜「Box<T>」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 437–456 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:437-456,搜「serious commitment」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 458–460 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:458-460,搜「apparent to the programmer」)。 ↩ ↩2 -
出处:「Chapter 3. Ownership and Moves」第 388–398 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:388-398,搜「pattern of bits」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 400–402 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:400-402,搜「pointless」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 412–455 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:412-455,搜「cannot be implemented」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 468 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:468,搜「atomic」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 475–483 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:475-483,搜「does not copy」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 491–497 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:491-497,搜「cannot borrow data」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 503 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:503,搜「simultaneously shared and mutable」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 505 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:505,搜「never be freed」)。 ↩ ↩2 -
出处:「Chapter 3. Ownership and Moves」第 509–511 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:509-511,搜「interior mutability」与「weak pointers」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 470–489 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:470-489,搜「shirataki」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 479–483 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:479-483,搜「last extant Rc」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 468 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:468,搜「performance penalty」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 505–509 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:505-509,搜「never be freed」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 511 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:511,搜「Weak」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 458 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:458,搜「byte-for-byte」)。 ↩ -
出处:「Chapter 3. Ownership and Moves」第 37 段(
text/13-ch03-chapter-3-ownership-and-moves.txt:37,搜「no-brainer」)。 ↩