令牌
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
|\\
|\0
UNICODE转义 :
\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
-9
a
-f
A
-F
]
整数字面值 的四种形式:
- 十进制字面值 以一个 十进制数字 开始,然后以任何 十进制数字 和 下划线 的混合。
- 十六进制字面值 以字符序列
U+0030
U+0078
(0x
) 开始,然后以任何十六进制数字和下划线混合(至少有一个数字)。 - 八进制字面值 以字符序列
U+0030
U+006F
(0o
) 开始,然后以任何八进制数字和下划线混合(至少有一个数字)。 - 二进制字面值 以字符序列
U+0030
U+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"..." {}} }