跳转至

表达式与控制流 — 分号是有意义的

这一章讲一件事: Rust 看着像 C,其实「if 有值、循环有值、块有值」—— 它是一门表达式语言。这个底色一变,分号、大括号、else 这些熟面孔全部有了新含义。 章首的题辞是 Alan Perlis 的毒舌:「Lisp 程序员知道一切东西的值,却不知道任何东西的代价。」1

1. 顶层全景:表达式做所有工作

  C 的世界:表达式(有值)与语句(没值)分得很清;if/for 是语句,不能嵌进表达式
      │
      ▼
  Rust 的世界:几乎所有东西都是表达式,都能放在「需要一个值」的地方
      │
      ├─ 块 { … } = 值是最后一个表达式的值;分号 = 丢弃值、变回 ()
      ├─ if / match = 值(分支必须同类型、必须穷尽)
      ├─ 循环 = 值(loop 配 break 带值)
      └─ 发散(! 类型)的表达式免于类型一致检查

图说:语法层的统一换来的是「少几种特殊构造」——没有三元运算符、没有 ++/--。

运算符优先级速查(从高到低)

表 5-1 的骨架,按优先级从高到低排列;同组同优先级,从左往右结合38:

组 运算符 备注
最高 路径 ::、方法调用 .、函数调用 ()、下标 []、宏 ! 字段访问 .x、元组字段 .0 也在这一层
一元 ! − * & &mut 逻辑非、负号、解引用、借
as as 类型转换,可与其他算术连用(左结合)
乘除余 * / % 整数除法向零取整
加减 + −
移位 << >>
位与 & 注意:比比较运算高,这是和 C 的不同
位异或、位或 ^ |
比较 == != < <= > >= 不可链式连用
逻辑与、或 && || 短路,操作数必须 bool
区间 .. ..= 不可链式连用
赋值 = += -= … 右结合,不可链式连用

容易踩的三处:方法调用优先级高于一元负号(上一章 -4.abs() 的坑); 位运算优先级高于比较(写位测试不用加括号);比较和赋值都不能连写 (a < b < c 和 a = b = c 都是语法错误)39。

2. 表达式语言:没有三元运算符的真正原因

C 里 5 * (fahr-32) / 9 是表达式、for (...) { ... } 是语句,界线分明。 Rust 里这条线几乎消失:if 和 match 产生值2。第一章那个上色语句就是个 match 表达式:

pixels[r * bounds.0 + c] = match escape_time(point, 255) {
    None => 0,
    Some(count) => 255 - count as u8,
};

if 也能初始化变量、match 能当函数参数传3。于是 C 的三元运算符 c ? a : b 在 Rust 里多余—— if 表达式两个分支都办事,「三元运算」只是它的一个特例4。 本章的主线例子就用这个上色语句往下走:match 的值 → 块当分支 → 分号的作用,一级级拆开。

3. 块与分号:值去哪了

块是最通用的表达式:产生一个值,可以用在任何需要值的地方5。 块的值 = 块里最后一个不带分号的表达式的值。分号干什么?把一个表达式变成语句,值被丢弃。

let msg = {
    let dandelion_control = puffball.open();      // let:分号必填
    dandelion_control.release_all_seeds(codes);   // 有分号:调了,返回值丢弃
    dandelion_control.get_status()                // 无分号:这个值就是块的值
};

(这是原书的原例,变量名保持原样:一块地里三个动作,前两个只要副作用,最后一个要结果。)

由此解释一个著名的新手报错。漏写分号:

if preferences.changed() {
    page.compute_size()   // 漏了分号
}

C 会提示「缺分号」;Rust 报:mismatched types: expected (), found (u32, u32)—— 没有分号,块的值就变成了 compute_size() 的返回值(一对 u32),而 if 没有 else 时分支类型必须是 ()。 好在编译器认得这个病,直接建议「try adding a semicolon」6。

带标签的块:块可以起名('trim: { … }),break 'trim 值 提前退出整个块并交出值—— 类似函数里的 return,适合一串「先做副作用、再定结果」的步骤7。

4. 声明:let、遮蔽、嵌套函数

let 声明局部变量,类型和初始值都可省,分号必填;可以先声明后赋值—— 只要每条路径上恰好初始化一次,连 mut 都不用加8。

遮蔽(shadowing):同一个名字再 let 一次,是第二个变量——新类型、新值,盖掉旧的:

for line in file.lines() {
    let line = line?;   // 第一个 line 是 Result<String>;第二个是 String
    ...
}

遮蔽在 Rust 代码里非常常见;它防的错是:名字现在指新值了,后面的代码想误用旧值都用不了9。 书里自己约定:教学时给第一个变量加 _result 后缀以示区分(如 line_result)10。

嵌套 item:块里还能声明函数、结构体、甚至整个模块。但嵌套 fn 看不见外层变量—— 要捕获环境,得用闭包(后面章节)11。

带标签块的完整例子

书上的完整例子是一个「判断换行符类型」的小函数片段,标签 'trim 让它读起来像一组带早退的步骤25:

let trimmed = 'trim: {
    if string.chars().last() != Some('\n') {
        break 'trim None;                  // 不是换行结尾:退出整块,值是 None
    }
    string.pop();
    if string.chars().last() != Some('\r') {
        break 'trim Some(Newline::Unix);  // 只吃掉了 \n:Unix 换行
    }
    string.pop();
    Some(Newline::Windows)               // \r\n 都吃掉:Windows 换行
};

三个出口,两个带值退出,一个正常落底——等价的 if-else 链会深三层。 作者提醒:这个写法适合「一串有副作用的步骤、每步都可能定结果」,滥用会不如普通分支好读26。

变量命名的宽松度

顺带一个轻松的事实:Rust 对标识符的「字母」定义极宽——希腊字母、带重音的拉丁字符都行, 只要 Unicode 标准认为它能当字母;emoji 不行。变量名须以字母或下划线开头,后面才能跟数字27。

5. if、match、if let、let-else

if:条件必须 bool;括号可省(写了还警告),大括号必填;无 else 等价于空 else12。

match:像 C 的 switch,灵活得多。形态是 match value { pattern => expr, … }: 按顺序匹配,命中即算完;至少一个分支必须命中——Rust 禁止不覆盖全部情况的 match13:

let score = match card.rank {
    Jack => 10,
    Queen => 10,
    Ace => 11,
};   // 错误:nonexhaustive patterns(没处理数字牌)

原书还交代了编译器怎么优化:分支是常量就查跳转表;每个分支产出常量值就建数组按值索引—— 除了边界检查没有任何分支,和 C++ 的 switch 同款待遇14。 分支类型必须一致;if 也一样,if c { 1 } else { 0 } 可以,if c { "a" } else { 9 } 不行, if c { "a" } 不行(没有 else 时,条件为假的结果是 (),和 "a" 对不上)15。 模式的全家桶(区间、解构、借用)留给后面章节,这里记住 _ 通配、必须穷尽、模式在前分支在后。

if let:一个模式的 match 的简写。if let Some(cookie) = request.session_cookie { … } 匹配成功走块、失败走 else;从来不是必需品(match 都能写),但省一层缩进16。

let-else:let 模式 = 表达式 else { 发散块 };——匹配不上就走发散块(必须发散: panic/return/无限循环),匹配上才继续往下17。经典场景:配置文件打不开就退出:

let Ok(config_file) = File::open(&config_path) else {
    panic!("Unable to open config file {}.", config_path.display());
};
let config = parse_config(config_file);   // 走到这里,config_file 一定是 Ok 里的值

块里还能装什么:item 声明

块不只装语句,还能装声明——任何能出现在全局的声明都能塞进块里:fn、struct、 use,甚至一整个模块。函数里套函数是完全合法的33:

fn show_files() -> io::Result<()> {
    let mut v = vec![];
    // ……
    fn cmp_by_timestamp_then_name(a: &FileInfo, b: &FileInfo) -> Ordering {
        a.timestamp.cmp(&b.timestamp).reverse()   // 新的在前
            .then(a.path.cmp(&b.path))            // 同名再比路径
    }
    v.sort_by(cmp_by_timestamp_then_name);
    // ……
}

有一条军规:嵌套的 fn 看不见外层的局部变量。cmp_by_timestamp_then_name 想用 v? 不行。要捕获环境,得用闭包(专章再讲)。作用域倒是覆盖整个块——声明在块的任何位置, 整块都可见,因为 fn 这类 item 在编译期就处理完了34。

为什么语言要留这个「正交性」?原书说了句实话:程序员(尤其写宏的程序员) 总会找到语言每一分正交性的用途35。

return 与 ?:能放弃手头的工作

return 看着平凡,在表达式语言里它有一个精确的角色:放弃正在求值的表达式, 直接从函数返回。第 1 章那个 ? 操作符展开后就是个带 return 的 match36:

let output = match File::create(filename) {
    Ok(f) => f,                    // 成功:整个 match 的值是 f,继续往下
    Err(err) => return Err(err),   // 失败:不管正在给谁赋值,立刻带错出函数
};

注意失败分支的语义:此刻我们正在计算 let output = … 的右边, return 一出现,这个未完成的赋值整个作废。这就是「发散表达式豁免于类型一致」的第一用途—— return Err(err) 的类型是 !,所以它出现在「值必须是 File」的上下文里也不冲突37。

match 的三条军规

把分散在例子里的规定收拢成军规40:

  1. 顺序就是优先级。模式从上到下依次试,命中即停;把宽泛的模式写在前面, 后面的分支就成了死代码——编译器会警告你。
  2. 通配符 _ 必须殿后。它兜住一切,放在前面会把后面的分支全毙掉(编译器同样警告)。
  3. 分支必须覆盖所有可能值——非穷尽(nonexhaustive patterns)是编译错误,不是警告。

三条的共同内核:match 不给你「漏掉情况」的机会。C 的 switch 漏了 default 只是风格问题, Rust 把它升格成类型检查——这正是「表达式语言」追求的一致性: 有值的东西,值的来历必须完整41。

6. 循环:四种形态与一个刻意的盲区

形态 说明
while 条件 { … } 同 C,条件必须 bool
while let 模式 = 表达式 { … } 每轮开头匹配:匹配上就跑,匹配不上就退出
loop { … } 无限循环,靠 break/return 出
for 模式 in 可迭代 { … } 遍历;C 的三段式 for 换成区间:for i in 0..20(末个打印 19)

循环也是表达式,但 while/for 的值恒为 ();loop 可以带值:break 值 就是循环表达式的值18。 for 循环遵守移动语义:for s in strings 把向量消耗掉(元素逐个搬进 s); for s in &strings 只借;for s in &mut strings 借可变、拿到 &mut String19。

break/continue:break 退出循环;带标签('search:)能退出外层循环; break 'outer i 标签和值可以同带。continue 跳下一轮20。

为什么 Rust 要有 loop? 这是原书给的设计内幕,值得单独记。编译器有几项流敏感分析: 每条路径是否都返回了正确类型、变量是否可能没初始化就被用、有没有不可达代码。 做这类分析要在「简单」和「聪明」之间选边:聪明的版本少误报,但程序员更难懂编译器在说什么。 Rust 选了简单:分析不看循环条件,假设任何条件都可能真也可能假。21

后果:这个完全安全的函数会被拒——

fn wait_for_process(process: &mut Process) -> i32 {
    while true {
        if process.wait() {
            return process.exit_code();
        }
    }
}   // 错误:expected i32, found ()——分析器不敢确定 while true 是死循环

loop 就是「说你想说的」的解法:它声明了「这就是个死循环」,类型检查满意放行22。

while let:每轮重新匹配

while let 是 if let 的循环版:每轮开头匹配一次,匹配上就跑,匹配不上就退出28。

for 与区间:C 循环的真正替身

C 的 for (int i = 0; i < 20; i++) 在 Rust 里是 for i in 0..20——最后打印 19。 0..20 不是个语法糖般的特殊东西,它是一个结构体值(std::ops::Range { start: 0, end: 20 }), 能拿去 for 循环是因为标准库给它实现了迭代接口;数组、切片、标准集合同理29。

for 循环与移动语义的三档用法(第 3 章的实战版):

写法 s 的类型 strings 的下场
for s in strings String 被消耗,循环后不可用
for s in &strings &String 只借,完好
for s in &mut strings &mut String 借可变,可改每个元素

想循环后还用集合,就迭代引用;想连容器一起处理掉,就迭代本体30。

break 带值:loop 的返回值

loop 的真身是「能产生值的表达式」,值从 break 交出来:

let answer = loop {
    if let Some(line) = next_line() {
        if line.starts_with("answer: ") {
            break line;                    // 找到就带值退出
        }
    } else {
        break "answer: nothing";           // 读到文件尾也退出
    }
};

规则:loop 里所有 break 带的值类型必须一致,那就是 loop 的类型; for/while 的 break 不许带值(它们的类型恒为 ())31。continue 照常跳下一轮, 也能带标签,跳到外层循环的下一轮32。

7. 发散与 ! 类型

有些表达式不照常结束:break/return 中途退场、loop 无 break 永不结束、panic!/process::exit 直接终止。 它们没有普通类型,被赋予特殊类型 !(发音「never」),并且豁免于「类型必须一致」—— 所以 if c { "a" } else { return } 合法:else 分支是 !,什么类型都接得住23。 fn exit(code: i32) -> ! 的签名读作:永不返回。自己也能写发散函数(比如 serve_forever)24。

8. 作者的判断与证据

  • 「Rust 选了简单」(流敏感分析不查循环条件)是明示的设计决策,代价是误拒少数安全程序 (while true 那例),收益是程序员能预测编译器的行为——判断标准写得很清楚21。
  • 「match 的常量分支编译成数组索引」是编译器实现细节,但属于可验证的既有行为14。
  • 「分号 = 丢弃值」是语义定义,不是风格问题——由此解释了报错信息的长相6。

9. 边界与局限

  • 模式匹配的完整能力(结构解构、守卫、绑定模式)本章只有预告,系统性讲解在未放出的章节;
  • ? 与 return 的配合只点到为止,错误处理的完整形态(error conversion 等)缺失;
  • 闭包被推迟——表达式层因此少了「函数即值」的一角。

10. 可带走的

主线例子一行版: match escape_time(point, 255) { None => 0, Some(c) => 255 - c } 当表达式赋给像素 → 分支可以是大块(块的值 = 最后无分号表达式的值)→ 分号丢弃值 → if/match 的值类型必须处处一致。

  1. Rust 是表达式语言:表达式做所有工作,if/match/块/循环都有值;
  2. 没有三元运算符,因为 if 表达式已经覆盖;
  3. 块的值 = 最后一个不带分号的表达式;分号 = 「调用、丢弃值」;
  4. 漏分号的报错是 mismatched types(不是缺分号)——因为块的类型变了;
  5. 同名再 let 是遮蔽:新变量盖旧变量,防止误用旧值;
  6. match 必须穷尽;分支同类型;if 无 else 时分支必须是 ();
  7. if let / let-else 是一个模式 match 的简写;let-else 的 else 必须发散;
  8. for 遵守移动语义:遍历值消耗它,遍历 &值只借;
  9. loop 存在的原因:类型检查的流敏感分析故意保持简单,不认 while true;
  10. ! 类型 = 永不结束;豁免于类型一致检查。

11. 原文地图

主题 原书章 原文位置
表达式语言定位 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:36(搜「expression language」)
无三元运算符 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:63(搜「ternary operator」)
块是最通用的表达式 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:253(搜「most general」)
分号 = 丢弃值 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:274(搜「dropped」)
漏分号的报错 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:292(搜「mismatched types」)
带标签的块 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:303(搜「labeled with a lifetime」)
遮蔽 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:348(搜「shadowing」)
嵌套 fn 不见外层 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:378(搜「cannot access」)
match 必须穷尽 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:431(搜「cover all possible values」) · text/15-ch05-chapter-5-expressions.txt:437(搜「nonexhaustive」)
分支类型一致 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:439(搜「same type」)
跳转表/数组优化 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:411(搜「jump table」)
if let 是简写 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:485(搜「never strictly necessary」)
let-else 与发散块 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:494(搜「only one acceptable pattern」) · text/15-ch05-chapter-5-expressions.txt:507(搜「diverging」)
循环四种形态 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:516(搜「four looping」)
for 的移动语义 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:558(搜「consumes the value」)
break 带标签带值 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:621(搜「labeled」) · text/15-ch05-chapter-5-expressions.txt:637(搜「outer」)
流敏感分析选简单 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:695(搜「went for simplicity」)
loop 是「说你想说的」 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:709(搜「say-what-you-mean」)
带标签块与 Unicode 命名 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:303(搜「'trim」)
for 的三档迭代 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:558(搜「consumes the value」)
break 带值的 loop Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:593(搜「answer: nothing」)
嵌套函数与 item 声明 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:357(搜「item」)
return 与 ? 的展开 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:666(搜「abandon」)
运算符优先级速查 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:67(搜「precedence」)
match 三条军规 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:409(搜「precedence」)
! 类型 Chapter 5. Expressions text/15-ch05-chapter-5-expressions.txt:713(搜「special type」) · text/15-ch05-chapter-5-expressions.txt:717(搜「divergent function」)

  1. 出处:「Chapter 5. Expressions」第 13–15 段(text/15-ch05-chapter-5-expressions.txt:13-15,搜「cost of nothing」)。Alan Perlis 的警句第 55 条。 ↩

  2. 出处:「Chapter 5. Expressions」第 36–38 段(text/15-ch05-chapter-5-expressions.txt:36-38,搜「expression language」)。 ↩

  3. 出处:「Chapter 5. Expressions」第 46–61 段(text/15-ch05-chapter-5-expressions.txt:46-61,搜「initialize a variable」)。 ↩

  4. 出处:「Chapter 5. Expressions」第 63 段(text/15-ch05-chapter-5-expressions.txt:63,搜「ternary operator」)。 ↩

  5. 出处:「Chapter 5. Expressions」第 253 段(text/15-ch05-chapter-5-expressions.txt:253,搜「most general」)。 ↩

  6. 出处:「Chapter 5. Expressions」第 282–301 段(text/15-ch05-chapter-5-expressions.txt:282-301,搜「mismatched types」)。 ↩↩

  7. 出处:「Chapter 5. Expressions」第 303–317 段(text/15-ch05-chapter-5-expressions.txt:303-317,搜「labeled with a lifetime」)。 ↩

  8. 出处:「Chapter 5. Expressions」第 327–339 段(text/15-ch05-chapter-5-expressions.txt:327-339,搜「initialized with a later assignment」)。 ↩

  9. 出处:「Chapter 5. Expressions」第 348 段(text/15-ch05-chapter-5-expressions.txt:348,搜「shadowing」)。 ↩

  10. 出处:「Chapter 5. Expressions」第 355 段(text/15-ch05-chapter-5-expressions.txt:355,搜「_result suffix」)。 ↩

  11. 出处:「Chapter 5. Expressions」第 357–378 段(text/15-ch05-chapter-5-expressions.txt:357-378,搜「cannot access」)。 ↩

  12. 出处:「Chapter 5. Expressions」第 384–398 段(text/15-ch05-chapter-5-expressions.txt:384-398,搜「curly braces」)。 ↩

  13. 出处:「Chapter 5. Expressions」第 422–437 段(text/15-ch05-chapter-5-expressions.txt:422-437,搜「cover all possible values」)。 ↩

  14. 出处:「Chapter 5. Expressions」第 411 段(text/15-ch05-chapter-5-expressions.txt:411,搜「jump table」)。 ↩↩

  15. 出处:「Chapter 5. Expressions」第 439–450 段(text/15-ch05-chapter-5-expressions.txt:439-450,搜「same type」)。 ↩

  16. 出处:「Chapter 5. Expressions」第 464–490 段(text/15-ch05-chapter-5-expressions.txt:464-490,搜「never strictly necessary」)。 ↩

  17. 出处:「Chapter 5. Expressions」第 494–512 段(text/15-ch05-chapter-5-expressions.txt:494-512,搜「only one acceptable pattern」)。 ↩

  18. 出处:「Chapter 5. Expressions」第 534 段(text/15-ch05-chapter-5-expressions.txt:534,搜「Loops are expressions」)。 ↩

  19. 出处:「Chapter 5. Expressions」第 556–580 段(text/15-ch05-chapter-5-expressions.txt:556-580,搜「consumes the value」)。 ↩

  20. 出处:「Chapter 5. Expressions」第 584–651 段(text/15-ch05-chapter-5-expressions.txt:584-651,搜「break」)。 ↩

  21. 出处:「Chapter 5. Expressions」第 683–695 段(text/15-ch05-chapter-5-expressions.txt:683-695,搜「went for simplicity」)。 ↩↩

  22. 出处:「Chapter 5. Expressions」第 697–709 段(text/15-ch05-chapter-5-expressions.txt:697-709,搜「bogus」)。 ↩

  23. 出处:「Chapter 5. Expressions」第 711–717 段(text/15-ch05-chapter-5-expressions.txt:711-717,搜「special type」)。 ↩

  24. 出处:「Chapter 5. Expressions」第 717–727 段(text/15-ch05-chapter-5-expressions.txt:717-727,搜「divergent function」)。 ↩

  25. 出处:「Chapter 5. Expressions」第 303–317 段(text/15-ch05-chapter-5-expressions.txt:303-317,搜「'trim」)。 ↩

  26. 出处:「Chapter 5. Expressions」第 317 段(text/15-ch05-chapter-5-expressions.txt:317,搜「more readable」)。 ↩

  27. 出处:「Chapter 5. Expressions」第 325 段(text/15-ch05-chapter-5-expressions.txt:325,搜「Unicode Standard Annex」)。 ↩

  28. 出处:「Chapter 5. Expressions」第 538 段(text/15-ch05-chapter-5-expressions.txt:538,搜「analogous」)。 ↩

  29. 出处:「Chapter 5. Expressions」第 542–556 段(text/15-ch05-chapter-5-expressions.txt:542-556,搜「IntoIterator」)。 ↩

  30. 出处:「Chapter 5. Expressions」第 558–580 段(text/15-ch05-chapter-5-expressions.txt:558-580,搜「consumes the value」)。 ↩

  31. 出处:「Chapter 5. Expressions」第 588–604 段(text/15-ch05-chapter-5-expressions.txt:588-604,搜「answer: nothing」)。 ↩

  32. 出处:「Chapter 5. Expressions」第 606–651 段(text/15-ch05-chapter-5-expressions.txt:606-651,搜「labeled」)。 ↩

  33. 出处:「Chapter 5. Expressions」第 357–378 段(text/15-ch05-chapter-5-expressions.txt:357-378,搜「item」)。 ↩

  34. 出处:「Chapter 5. Expressions」第 378 段(text/15-ch05-chapter-5-expressions.txt:378,搜「cannot access」)。 ↩

  35. 出处:「Chapter 5. Expressions」第 380 段(text/15-ch05-chapter-5-expressions.txt:380,搜「orthogonality」)。 ↩

  36. 出处:「Chapter 5. Expressions」第 666–679 段(text/15-ch05-chapter-5-expressions.txt:666-679,搜「abandon」)。 ↩

  37. 出处:「Chapter 5. Expressions」第 711–717 段(text/15-ch05-chapter-5-expressions.txt:711-717,搜「exempt」)。 ↩

  38. 出处:「Chapter 5. Expressions」第 67–249 段(text/15-ch05-chapter-5-expressions.txt:67-249,搜「precedence」)。分组依据表 5-1,结合性说明见第 245–249 段(搜「left-associative」)。 ↩

  39. 出处:「Chapter 5. Expressions」第 249 段(text/15-ch05-chapter-5-expressions.txt:249,搜「can't be chained」)。 ↩

  40. 出处:「Chapter 5. Expressions」第 400–431 段(text/15-ch05-chapter-5-expressions.txt:400-431,搜「wildcard」)。 ↩

  41. 出处:「Chapter 5. Expressions」第 431–437 段(text/15-ch05-chapter-5-expressions.txt:431-437,搜「prohibits」)。 ↩