令牌
Token 是非递归的常规编程语言描述语法的原始制品。 Rust 源码解析时 Token 有以下几种 :
在本文档的语法表示部分,"简单" Token 以 string 产生式 的形式给出,并以 monospace "等宽" 字体呈现。
译注: 中译本无法表达等宽字体,如有必要请至译本仓库查看对应的翻译词条。
字面值
字面值 Token 用于 字面值表达式 。
示例
字符和字符串
| 示例 | # 标记* | 字符集 | 转义 | |
|---|---|---|---|---|
| 字符 | 'H' | 0 | All Unicode | 引号 & ASCII & Unicode | 
| 字符串 | "hello" | 0 | All Unicode | 引号 & ASCII & Unicode | 
| 原始字符串 | r#"hello"# | <256 | All Unicode | N/A | 
| 字节 | b'H' | 0 | All ASCII | 引号 & Byte | 
| 字节字符串 | b"hello" | 0 | All ASCII | 引号 & Byte | 
| 原始字节字符串 | br#"hello"# | <256 | All ASCII | N/A | 
* 同一字面值两侧 # 的数量必须相等。
ASCII 转义符
| 名称 | |
|---|---|
\x41 | 7位字符编码(确切说是 2 位数字,最高为 0x7F ) | 
\n | 换行 | 
\r | 回车 | 
\t | 制表符 | 
\\ | 反斜杠 | 
\0 | 空 | 
字节转义符
| 名称 | |
|---|---|
\x7F | 8位字符编码 (确切地说是 2 位数字) | 
\n | 换行 | 
\r | 回车 | 
\t | 制表符 | 
\\ | 反斜杠 | 
\0 | 空字符 | 
Unicode 转义符
| 名称 | |
|---|---|
\u{7FFF} | 24 位 Unicode 字符编码 (最多 6 位) | 
引号转义符
| 名称 | |
|---|---|
\' | 单引号 | 
\" | 双引号 | 
数字
数字字面值* | 示例 | 指数运算 | 
|---|---|---|
| 十进制整数 | 98_222 | N/A | 
| 十六进制整数 | 0xff | N/A | 
| 八进制整数 | 0o77 | N/A | 
| 二进制整数 | 0b1111_0000 | N/A | 
| 浮点数 | 123.0E+77 | 可选的 | 
* 所有的数字字面值允许 _ 作为可视化分隔符: 1_234.0E+18f64
后缀
后缀是在字面值主体部分之后的一串字符(中间没有空白),其形式与非原始标识符或关键字相同。
词法
后缀 : 标识符或关键字
后缀非E : 后缀 不以e或E开始
任何种类的字面值字符串、整数等可带有任意后缀,将当作单个 Token 。
带有任意后缀的字面值 Token 可以传递给宏而不产生错误。
宏本身去决定如何解释这类 Token 以及是否产生错误。
特别是,实例宏的 literal 片段指示器可以匹配具有任意后缀的字面值 Token。
#![allow(unused)] fn main() { macro_rules! blackhole { ($tt:tt) => () } macro_rules! blackhole_lit { ($l:literal) => () } blackhole!("string"suffix); // OK blackhole_lit!(1suffix); // OK }
然而,在被解释为字面值表达式或模式时,其字面值 Token 的后缀是受限的。 拒绝非数字字面值 Token 上的任意后缀。 而数字字面值 Token 只接受以下列表中的后缀。
| 整数 | 浮点数 | 
|---|---|
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize | f32, f64 | 
字符和字符串字面值
字符字面值
词法
字符字面值 :
'( ~['\\n \r \t] | 引号转义 | ASCII转义 | UNICODE转义 )'后缀?引号转义 :
\'|\"ASCII转义 :
\x八进制数 十六进制数
|\n|\r|\t|\\|\0UNICODE转义 :
\u{( 十六进制数_* )1..6}
字符字面值 是指被两个 U+0027 (单引号) 字符包围的单个 Unicode 字符,
但 U+0027 本身除外,单引号必须在前面以 U+005C 字符 (\)  转义 。
字符串字面值
词法
字符串字面值 :
"(
~["\孤立CR]
| 引用转义
| ASCII转义
| UNICODE转义
| 字符串延续
)*"后缀?字符串延续 :
\随后 \n
字符串字面值 是由两个 U+0022 (双引号) 字符包围的任意 Unicode 字符序列,
但 U+0022 本身除外,双引号必须在前面以 U+005C 字符 (\)  转义 。
字符串字面值中允许断行,换行符 (U+000A) 或一对回车和换行符 (U+000D, U+000A) ,
这两个字节序列通常被转换成 U+000A ,但有一种例外,
当一个未转义的 U+005C  (\) 字符出现在断行之前,那么忽略断行符和所有紧随其后的   (U+0020) 、 \t (U+0009) 、 \n (U+000A) 、 \r (U+0000D) 字符。
因此 a 、 b 、 c 相同:
#![allow(unused)] fn main() { let a = "foobar"; let b = "foo\ bar"; let c = "foo\ bar"; assert_eq!(a, b); assert_eq!(b, c); }
注意: 因为允许额外的换行 (比如在例子
c中) ,这有可能会让人感到意外。 在未来可能会调整这种行为。在做出决定之前,建议避免使用,也就是说,目前,会跳过多个连续的换行。 更多内容见这个 Issue 。
字符转义
可以在字符或非原始字符串字面值中使用一些额外的 转义 ,
转义以 U+005C (\) 开始,并以下列形式延续:
- 7 位编码转义 以 
U+0078(x) 开始,后面正好有两个数值不超过0x7F的 十六进制数字 。 它表示 ASCII 字符,其值等于提供的十六进制值。不允许更高的值,因为无法明确是指 Unicode 编码还是字节值。 - 24 位编码转义 以 
U+0075(u) 开始,后面是最多六个 十六位数字 ,由大括号U+007B({) 和U+007D(}) 包围。它表示 Unicode 编码,等于所提供的十六进制值。 - 空白转义 是字符 
U+006E(n) 、U+0072(r) 或U+0074(t) 之一,分别表示 Unicode 值U+000A(LF) 、U+000D(CR) 或U+0009(HT) 。 - null 转义 是字符 
U+0030(0) ,表示 Unicode 值U+0000(NUL) 。 - 反斜线转义 是字符 
U+005C(\) ,必须经过转义以表示自身。 
原始字符串字面值
词法
原始字符串字面值 :
r原始字符串上下文 后缀?原始字符串上下文 :
"( ~ 孤立CR )* (非贪婪)"
|#原始字符串上下文#
原始字符串字面值不处理任何转义。
以字符 U+0072 (r) 开始,后面是少于 256 个的 U+0023 (#) 和 U+0022 (双引号) 字符。
原始字符串主体 可以包含任何 Unicode 字符序列,并且只能由另一个 U+0022 (双引号) 字符结束,后面是 U+0022 (双引号) 字符与开头相同数量的 U+0023 (#) 字符。
原始字符串主体中包含的所有 Unicode 字符都表示自己。
字符 U+0022 (双引号)  或 U+005C (\) 不具有任何特殊含义 (除非后面有至少相同数量的 U+0023 (#) 字符用于表达原始字符串字面值)。
字符串字面值示例:
#![allow(unused)] fn main() { "foo"; r"foo"; // foo "\"foo\""; r#""foo""#; // "foo" "foo #\"# bar"; r##"foo #"# bar"##; // foo #"# bar "\x52"; "R"; r"R"; // R "\\x52"; r"\x52"; // \x52 }
字节和字节字符串字面值
字节字面值
词法
字节字面值 :
b'( ASCII字符 | 字节转义 )'后缀?ASCII字符 :
任意ASCII (如 0x00 至 0x7F), 不包括',\, \n, \r 或 \t字节转义 :
\x十六进制数 十六进制数
|\n|\r|\t|\\|\0|\'|\"
字节字面值 表示是一个 ASCII 字符 (在 U+0000 到 U+007F 范围内) 或单一的 转义 ,前面是字符 U+0062 (b) 和 U+0027 (单引号),后面是字符 U+0027 。
如果字面值有 U+0027 字符,必须由前置 U+005C (\) 字符来 转义 。其相当于一个 u8 无符号 8 位整数 数字字面值 。
字节字符串字面值
词法
字节字符串字面值 :
b"( ASCII字符串 | 字节转义 | 字符串延续 )*"后缀?ASCII字符串 :
任意ASCII (如 0x00 至 0x7F), 不包括",\和 孤立CR
非原始的 字节字符串字面值 是一串 ASCII 字符和 转义 。
前面是字符 U+0062 (b) 和 U+0022 (双引号) ,后面是字符 U+0022 。
如果字面值有 U+0022 字符,必须前置 U+005C (\) 字符来转义。
或者,字节字符串字面值是 原始字节字符串字面值 ,定义如下。
长度为 n 的字节字符串字面值的类型是 &'static [u8; n] 。
一些额外的 转义 在字节或非原始字节的字符串字面值中都是可用的。
转义以 U+005C (\) 开始,并以下列形式延续。
- 字节转义 以 
U+0078(x) 开始,后面正好有两个 十六进制数字 。表示相当于所提供的十六进制值的字节。 - 空白转义 是字符 
U+006E(n) 、U+0072(r) ,或U+0074(t) ,分别表示字节值0x0A(ASCII LF) 、0x0D(ASCII CR) 或0x09(ASCII HT) 。 - null 转义 是字符 
U+0030(0) ,表示字节值0x00(ASCII NUL) 。 - 反斜线转义 是字符 
U+005C(\) ,必须转义以表示其 ASCII 编码0x5C。 
原始字节字符串字面值
词法
原始字节字符串字面值 :
br原始字节字符串正文 后缀?原始字节字符串正文 :
"ASCII* (非贪婪)"
|#原始字节字符串正文#ASCII :
任意ASCII (如 0x00 至 0x7F)
原始字节字符串字面值不处理任何转义。
它们以字符 U+0062 (b) 开始,然后是 U+0072 (r) ,后面是少于 256 的字符 U+0023 (#),以及一个 U+0022 (双引号) 字符。
原始字符串主体 可以包含任何 ASCII 字符序列,并且只能由另一个 U+0022 (双引号) 字符终止,后面是 U+0022 (双引号) 字符与开头数量相同 U+0023 (#) 字符。
原始的字节字符串字面值不能包含任何非 ASCII 字节。
原始字符串主体中包含的所有字符表示其 ASCII 编码。
字符 U+0022 (双引号) 或 U+005C (\) 不具有任何特殊含义 (除非后面有至少相同数量的 U+0023 (#) 字符表达原始字符串字面值) 。
字符串字面值示例:
#![allow(unused)] fn main() { b"foo"; br"foo"; // foo b"\"foo\""; br#""foo""#; // "foo" b"foo #\"# bar"; br##"foo #"# bar"##; // foo #"# bar b"\x52"; b"R"; br"R"; // R b"\\x52"; br"\x52"; // \x52 }
数字字面值
数字字面值 是一个 整数字面值 或 浮点字面值 。语法上混合识别这两种字面值。
整数字面值
词法
整数字面值 :
( 十进制字面值 | 二进制字面值 | 八进制字面值 | 十六进制字面值 ) 非E后缀?十进制字面值 :
十进制数 (十进制数|_)*二进制字面值 :
0b(二进制数|_)* 二进制数 (二进制数|_)*八进制字面值 :
0o(八进制数|_)* 八进制数 (八进制数|_)*十六进制字面值 :
0x(十六进制数|_)* 十六进制数 (十六进制数|_)*十进制数 : [
0-1]二进制数 : [
0-7]八进制数 : [
0-9]十六进制数 : [
0-9a-fA-F]
整数字面值 的四种形式:
- 十进制字面值 以一个 十进制数字 开始,然后以任何 十进制数字 和 下划线 的混合。
 - 十六进制字面值 以字符序列 
U+0030U+0078(0x) 开始,然后以任何十六进制数字和下划线混合(至少有一个数字)。 - 八进制字面值 以字符序列 
U+0030U+006F(0o) 开始,然后以任何八进制数字和下划线混合(至少有一个数字)。 - 二进制字面值 以字符序列 
U+0030U+0062(0b) 开始,然后以任何二进制数字和下划线混合(至少有一个数字)。 
像其他字面值一样,整数字面值可以在后面加一个后缀(紧随而没有空格),如上所述。
后缀不能以 e 或 E 开头,因为这将被解释为浮点字面值的指数。
关于这些后缀的实现,见 字面值表达式 。
字面值表达式允许的整数字面值的示例:
#![allow(unused)] fn main() { #![allow(overflowing_literals)] 123; 123i32; 123u32; 123_u32; 0xff; 0xff_u8; 0x01_f32; // 整数 7986, 非浮点 1.0 0x01_e3; // 整数 483, 非浮点 1000.0 0o70; 0o70_i16; 0b1111_1111_1001_0000; 0b1111_1111_1001_0000i64; 0b________1; 0usize; // 这对于其类型来说值太大,但允许在字面值表达式。 128_i8; 256_u8; // 这是整数字面值,允许在浮点字面值表达式。 5f32; }
注意,比如 -1i8 将被解析为两个标记: - 和 1i8。
字面值表达式不允许的整数字面值示例:
#![allow(unused)] fn main() { #[cfg(FALSE)] { 0invalidSuffix; 123AFB43; 0b010a; 0xAB_CD_EF_GH; 0b1111_f32; } }
元组索引
词法
元组索引:
整数字面值
元组索引用于指代 元组 、元组结构体 和 元组变体 的字段。
元组索引直接与字面值 Token 进行比对。元组索引从 0 开始,每一个连续的索引其值增加 1 ,为十进制值。
因此,只有十进制的值才能匹配,其值不能有任何额外的 0 前缀字符。
#![allow(unused)] fn main() { let example = ("dog", "cat", "horse"); let dog = example.0; let cat = example.1; // 下面的例子是无效的。 let cat = example.01; // ERROR 字段名不能为 `01` let horse = example.0b10; // ERROR 字段名不能为 `0b10` }
注意: 元组索引可能包括某些后缀,但这并不意味着是有效的,在未来的版本中可能会删除。 更多内容见 https://github.com/rust-lang/rust/issues/60210 。
浮点字面值
词法
浮点字面值 :
十进制数.(不是紧跟着.,_或 XID_起始 字符)
| 十进制字面值.十进制字面值 后缀非E?
| 十进制字面值 (.十进制字面值)? 浮点指数 后缀?浮点指数 :
(e|E) (+|-)? (十进制数|_)* 十进制数 (十进制数|_)*
浮点字面值 两种形式:
- 十进制字面值 后面一个句号字符 
U+002E(.),随后是可选的另一个十进制的字面值,并有一个可选的 指数 。 - 单一的 十进制字面值 后随一个 指数 。
 
和整数字面值一样,浮点字面值后面可以有后缀,只要前缀部分不以 U+002E (.) 结尾。
如果字面值内容不包含指数,后缀不能以 e 或 E 开头。
关于这些后缀的实现,请参见 字面值表达式。
字面值表达式允许的的浮点字面值的示例:
#![allow(unused)] fn main() { 123.0f64; 0.1f64; 0.1f32; 12E+99_f64; let x: f64 = 2.; }
最后一个例子是不同的,因为不能对以句点结尾的浮点字面值使用后缀语法。
2.f64 将试图在 2 上调用一个名为 f64 的方法。
注意,比如 -1.0 将被解析为两个符号: - 和 1.0 。
字面值表达式不允许的浮点字面值示例:
#![allow(unused)] fn main() { #[cfg(FALSE)] { 2.0f80; 2e5f80; 2e5e6; 2.0e5e6; 1.3e10u64; } }
类似于数字字面值的保留形式
词法
保留数 :
二进制字面值 [2-9&零空白;]
| 八进制字面值 [8-9&零空白;]
| ( 二进制字面值 | 八进制字面值 | 十六进制字面值 ).
(不是紧随着.,_或 XID_起始 字符)
| ( 二进制字面值 | 八进制字面值 ) (e|E)
|0b_* 输入结束或非二进制数
|0o_* 输入结束或非八进制数
|0x_* 输入结束或非十六进制数
| 十进制字面值 ( . 十进制字面值)? (e|E) (+|-)? 输入结束或非十进制数
以下类似于数字字面值的词法是 保留形式 。 由于这些形式可能引起歧义,编译器会拒绝它们,而不会解释为独立的 Token 。
- 
一个无后缀的二进制或八进制字面值,中间没有空白,随后是一个超出其小数范围的十进制数字。
 - 
一个无后缀的二进制、八进制或十六进制字面值,中间没有空白,随后是一个点号(对点号后面的限制与浮点字面值相同)。
 - 
一个无后缀的二进制或八进制字面值,中间没有空白,随后是字符
e或E组成。 - 
以一个小数点前缀开始的输入,但不是一个有效的二进制、八进制或十六进制字面值(因为它不包含数字)。
 - 
具有浮点字面值形式的输入,指数中没有数字。
 
保留形式的示例:
#![allow(unused)] fn main() { 0b0102; // 这不是 `0b010` 随后有 `2` 0o1279; // 这不是 `0o127` 随后有 `9` 0x80.0; // 这不是 `0x80` 随后有 `.` 和 `0` 0b101e; // 这不是有后缀的字面值或 `0b101` 随后有 `e` 0b; // 这不是一个整数字面值或 `0`随后有 `b` 0b_; // 这不是一个整数字面值或 `0` 随后有 `b_` 2e; // 这不是一个浮点字面值或 `2` 随后有 `e` 2.0e; // 这不是一个浮点字面值或 `2.0` 随后有 `e` 2em; // 这不是有后缀的字面值或 `2` 随后有 `em` 2.0em; // 这不是有后缀的字面值或 `2.0` 随后有 `em` }
生命周期和循环标签
词法
生命周期TOKEN :
'标识符或关键字
|'_生命周期或标签 :
'非关键字标识符
生命周期参数和 循环标签 使用 '生命周期或标签' token 。词法分析器接收任何 '生命周期TOKEN' ,比如,能够在宏中使用。
标点符号
为了完整,这里列出了标点符号 Token 。它们各自的用途和含义定义在对应的链接页面中。
| 符号 | 名称 | 用法 | 
|---|---|---|
+ | 加号 | 加法,trait约束,宏重复匹配器 | 
- | 减号 | 减法,否定 | 
* | 星号 | 乘法,解引用,原始指针,宏重复匹配器,[用作通配符][wildcards] | 
/ | 斜线 | 除法 | 
% | 百分号 | 取余 | 
^ | 插入符号 | 位运算异或和逻辑运算异或 | 
! | 感叹号 | 位运算非和逻辑运算非,宏调用,内部属性,永不类型,否定的impl | 
& | 与符号 | 位运算与和逻辑运算与,借用,引用,引用模式 | 
| | 或符号 | 位运算或和逻辑运算或,闭包,模式匹配中的模式,if let和while let中的模式 | 
&& | 与运算符 | 惰性与运算,借用,引用,引用模式 | 
|| | 或运算符 | 惰性或运算,闭包 | 
<< | 左移运算符 | 左移,嵌套泛型 | 
>> | 右移运算符 | 右移,嵌套泛型 | 
+= | 加等于运算符 | 加法赋值 | 
-= | 减等于运算符 | 减法赋值 | 
*= | 乘等于运算符 | 乘法赋值 | 
/= | 除等于运算符 | 除法赋值 | 
%= | 取余等于运算符 | 取余赋值 | 
^= | 异或等于运算符 | 位异或赋值 | 
&= | 与等于运算符 | 位与赋值 | 
|= | 或等于运算符 | 位或赋值 | 
<<= | 左移等于运算符 | 左移赋值 | 
>>= | 右移等于运算符 | 右移赋值, 嵌套泛型 | 
= | 等号 | 赋值,属性,各种类型定义 | 
== | 双等号 | 等于 | 
!= | 不等于号 | 不等于 | 
> | 大于号 | 大于,泛型,路径 | 
< | 小于号 | 小于,泛型,路径 | 
>= | 大于等于号 | 大于等于,泛型 | 
<= | 小于等于号 | 小于等于 | 
@ | At | 子模式绑定 | 
_ | 下划线 | 通配符模式,推断类型,常量、extern crates、use 声明和解构赋值中的匿名条目 | 
. | 点号 | 字段访问,元组索引 | 
.. | 双点号 | 区间,结构体表达式,模式,区间模式rangepat | 
... | 三点号 | 可变参数函数,区间模式 | 
..= | 双点等号 | 闭区间,区间模式 | 
, | 逗号 | 各种分隔符 | 
; | 分号 | 用于各种条目和语句的终止符,数组类型 | 
: | 冒号 | 各种分隔符 | 
:: | 路径分隔符 | 路径分隔符 | 
-> | 箭头符 | 函数返回类型,闭包返回类型,函数指针类型 | 
=> | 双箭头符 | 匹配分支,宏 | 
# | 井号 | 属性 | 
$ | 美元符 | 宏 | 
? | 问号 | 问号运算符,大小可变,宏重复匹配器 | 
~ | 波浪符 | Rust 1.0 之前就已经不再使用了,但其 token 仍可使用 | 
定界符号
括号符号用在语法的各部分。一个左括号必须总是与一个右括号相配对。括号和其中的 Token 在 宏 中被称为 "token 树" 。三种类型的括号是:
| 括号 | 类型 | 
|---|---|
{ } | 大括号 | 
[ ] | 方括号 | 
( ) | 圆括号 | 
保留前缀
词法 2021+
保留TOKEN双引号: (标识符或关键字不包括b或r或br|_)"
保留TOKEN单引号: (标识符或关键字不包括b_ |_)'
保留TOKEN井号: (标识符或关键字不包括r或br|_)#
一些被称为 保留前缀 的词法形式被保留下来,供将来使用。
如果源码输入在词法上被解析为非原始标识符(或关键字或 _ ),紧随其后的是 # 、 ' 或 " 字符(没有中间的空白) ,刚将其标识为保留前缀。
请注意,原始标识符、原始字符串字面值和原始字节字符串字面值可能包含 # 字符,但不会被解释为包含保留前缀。
同样,在原始字符串字面值、字节字面值、字节字符串字面值和原始字节字符串字面值中使用的 r 、 b 和 br 前缀也不被解释为保留前缀。
版次差异: 从 2021 版开始,保留前缀会被词法分析器报告为错误 (尤其不能传递给宏)。
在 2021 版本之前,保留前缀被词法分析器接受,并被解释为多个 Token (比如,Token 为标识符或关键词,后面是
#token)。所有版次允许的示例:
#![allow(unused)] fn main() { macro_rules! lexes {($($_:tt)*) => {}} lexes!{a #foo} lexes!{continue 'foo} lexes!{match "..." {}} lexes!{r#let#foo} // 三个 token: r#let # foo }示例在 2021 版本之前是允许的,之后不允许:
#![allow(unused)] fn main() { macro_rules! lexes {($($_:tt)*) => {}} lexes!{a#foo} lexes!{continue'foo} lexes!{match"..." {}} }