令牌

Token 是非递归的常规编程语言描述语法的原始制品。 Rust 源码解析时 Token 有以下几种 :

在本文档的语法表示部分,"简单" Token 以 string 产生式 的形式给出,并以 monospace "等宽" 字体呈现。

译注: 中译本无法表达等宽字体,如有必要请至译本仓库查看对应的翻译词条。

字面值

字面值 Token 用于 字面值表达式

示例

字符和字符串

示例# 标记*字符集转义
字符'H'0All Unicode引号 & ASCII & Unicode
字符串"hello"0All Unicode引号 & ASCII & Unicode
原始字符串r#"hello"#<256All UnicodeN/A
字节b'H'0All ASCII引号 & Byte
字节字符串b"hello"0All ASCII引号 & Byte
原始字节字符串br#"hello"#<256All ASCIIN/A

* 同一字面值两侧 # 的数量必须相等。

ASCII 转义符

名称
\x417位字符编码(确切说是 2 位数字,最高为 0x7F )
\n换行
\r回车
\t制表符
\\反斜杠
\0

字节转义符

名称
\x7F8位字符编码 (确切地说是 2 位数字)
\n换行
\r回车
\t制表符
\\反斜杠
\0空字符

Unicode 转义符

名称
\u{7FFF}24 位 Unicode 字符编码 (最多 6 位)

引号转义符

名称
\'单引号
\"双引号

数字

数字字面值*示例指数运算
十进制整数98_222N/A
十六进制整数0xffN/A
八进制整数0o77N/A
二进制整数0b1111_0000N/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, isizef32, 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) 字符。 因此 abc 相同:

#![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+0000U+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) 开始,然后以任何二进制数字和下划线混合(至少有一个数字)。

像其他字面值一样,整数字面值可以在后面加一个后缀(紧随而没有空格),如上所述。 后缀不能以 eE 开头,因为这将被解释为浮点字面值的指数。 关于这些后缀的实现,见 字面值表达式

字面值表达式允许的整数字面值的示例:

#![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 (.) 结尾。 如果字面值内容不包含指数,后缀不能以 eE 开头。 关于这些后缀的实现,请参见 字面值表达式

字面值表达式允许的的浮点字面值的示例:

#![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 。

  • 一个无后缀的二进制或八进制字面值,中间没有空白,随后是一个超出其小数范围的十进制数字。

  • 一个无后缀的二进制、八进制或十六进制字面值,中间没有空白,随后是一个点号(对点号后面的限制与浮点字面值相同)。

  • 一个无后缀的二进制或八进制字面值,中间没有空白,随后是字符 eE 组成。

  • 以一个小数点前缀开始的输入,但不是一个有效的二进制、八进制或十六进制字面值(因为它不包含数字)。

  • 具有浮点字面值形式的输入,指数中没有数字。

保留形式的示例:

#![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 letwhile let中的模式
&&与运算符惰性与运算借用引用引用模式
||或运算符惰性或运算闭包
<<左移运算符左移嵌套泛型
>>右移运算符右移嵌套泛型
+=加等于运算符加法赋值
-=减等于运算符减法赋值
*=乘等于运算符乘法赋值
/=除等于运算符除法赋值
%=取余等于运算符取余赋值
^=异或等于运算符位异或赋值
&=与等于运算符位与赋值
|=或等于运算符位或赋值
<<=左移等于运算符左移赋值
>>=右移等于运算符右移赋值, 嵌套泛型
=等号赋值属性,各种类型定义
==双等号等于
!=不等于号不等于
>大于号大于泛型路径
<小于号小于泛型路径
>=大于等于号大于等于泛型
<=小于等于号小于等于
@At子模式绑定
_下划线通配符模式推断类型常量extern cratesuse 声明解构赋值中的匿名条目
.点号字段访问元组索引
..双点号区间结构体表达式模式区间模式rangepat
...三点号可变参数函数区间模式
..=双点等号闭区间区间模式
,逗号各种分隔符
;分号用于各种条目和语句的终止符,数组类型
:冒号各种分隔符
::路径分隔符路径分隔符
->箭头符函数返回类型闭包返回类型函数指针类型
=>双箭头符匹配分支
#井号属性
$美元符
?问号问号运算符大小可变宏重复匹配器
~波浪符Rust 1.0 之前就已经不再使用了,但其 token 仍可使用

定界符号

括号符号用在语法的各部分。一个左括号必须总是与一个右括号相配对。括号和其中的 Token 在 中被称为 "token 树" 。三种类型的括号是:

括号类型
{ }大括号
[ ]方括号
( )圆括号

保留前缀

词法 2021+
保留TOKEN双引号: (标识符或关键字不包括 brbr | _ ) "
保留TOKEN单引号: (标识符或关键字不包括 b_ | _ ) '
保留TOKEN井号: (标识符或关键字不包括 rbr | _ ) #

一些被称为 保留前缀 的词法形式被保留下来,供将来使用。

如果源码输入在词法上被解析为非原始标识符(或关键字或 _ ),紧随其后的是 #'" 字符(没有中间的空白) ,刚将其标识为保留前缀。

请注意,原始标识符、原始字符串字面值和原始字节字符串字面值可能包含 # 字符,但不会被解释为包含保留前缀。

同样,在原始字符串字面值、字节字面值、字节字符串字面值和原始字节字符串字面值中使用的 rbbr 前缀也不被解释为保留前缀。

版次差异: 从 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"..." {}}
}