1、Rust参考手册——记法和词法结构

目录

  • [0. 引言](#0. 引言)
  • [1. 记法](#1. 记法)
    • [1.1 语法](#1.1 语法)
  • [2. 词法结构](#2. 词法结构)
    • [2.1 输入格式](#2.1 输入格式)
      • [2.1.1 源文件编码](#2.1.1 源文件编码)
      • [2.2.2 BOM移除](#2.2.2 BOM移除)
      • [2.2.3 换行符归一化](#2.2.3 换行符归一化)
      • [2.2.4 shebang移除](#2.2.4 shebang移除)
      • [2.2.5 分词](#2.2.5 分词)
    • [2.2 Whitespace](#2.2 Whitespace)
    • [2.3 Comments](#2.3 Comments)
      • [2.3.1 非文档注释](#2.3.1 非文档注释)
      • [2.3.2 文档注释](#2.3.2 文档注释)
      • [2.3.3 示例](#2.3.3 示例)
    • [2.4 Shebang](#2.4 Shebang)
      • [2.4.1 示例](#2.4.1 示例)
    • [2.5 Keywords(具体关键词后面会讲)](#2.5 Keywords(具体关键词后面会讲))
      • [2.5.1 Strict Keywords](#2.5.1 Strict Keywords)
      • [2.5.2 Reserved Keywords](#2.5.2 Reserved Keywords)
      • [2.5.3 Weak Keywords](#2.5.3 Weak Keywords)
      • [2.5.4 附录](#2.5.4 附录)
    • [2.6 Identifiers](#2.6 Identifiers)
      • [2.6.1 标识符具体要求](#2.6.1 标识符具体要求)
      • [2.6.2 归一化(没啥意义)](#2.6.2 归一化(没啥意义))
      • [2.6.3 Raw identifiers](#2.6.3 Raw identifiers)
        • [2.6.3.1 用处](#2.6.3.1 用处)
    • [2.7 Tokens](#2.7 Tokens)
      • [2.7.1 Literals](#2.7.1 Literals)
        • [2.7.1.1 示例](#2.7.1.1 示例)
          • [2.7.1.1.1 字符与字符串](#2.7.1.1.1 字符与字符串)
          • [2.7.1.1.2 ASCII转义序列](#2.7.1.1.2 ASCII转义序列)
          • [2.7.1.1.3 字节转义序列](#2.7.1.1.3 字节转义序列)
          • [2.7.1.1.4 Unicode转义序列](#2.7.1.1.4 Unicode转义序列)
          • [2.7.1.1.5 引号转义序列](#2.7.1.1.5 引号转义序列)
          • [2.7.1.1.6 数字](#2.7.1.1.6 数字)
          • [2.7.1.1.7 后缀(这里举了声明宏怎么处理后缀,但是宏模式语法又提及)](#2.7.1.1.7 后缀(这里举了声明宏怎么处理后缀,但是宏模式语法又提及))
        • [2.7.1.2 字符与字符串字面量(字面量表达式不能有后缀,其他情况可以有后缀)](#2.7.1.2 字符与字符串字面量(字面量表达式不能有后缀,其他情况可以有后缀))
          • [2.7.1.2.1 字符字面量](#2.7.1.2.1 字符字面量)
          • [2.7.1.2.2 字符串字面量](#2.7.1.2.2 字符串字面量)
          • [2.7.1.2.3 原始字符串字面量](#2.7.1.2.3 原始字符串字面量)
        • [2.7.1.3 字节与字节串字面量(字面量表达式不能有后缀,其他情况可以有后缀)](#2.7.1.3 字节与字节串字面量(字面量表达式不能有后缀,其他情况可以有后缀))
          • [2.7.1.3.1 字节字面量](#2.7.1.3.1 字节字面量)
          • [2.7.1.3.2 字节串字面量](#2.7.1.3.2 字节串字面量)
          • [2.7.1.3.3 原始字节串字面量](#2.7.1.3.3 原始字节串字面量)
        • [2.7.1.4 C字符串与原始C字符串字面量(字面量表达式不能有后缀,其他情况可以有后缀)](#2.7.1.4 C字符串与原始C字符串字面量(字面量表达式不能有后缀,其他情况可以有后缀))
          • [2.7.1.4.1 C字符串字面量(rust 2021之后才有)](#2.7.1.4.1 C字符串字面量(rust 2021之后才有))
          • [2.7.1.4.2 原始C字符串字面量(rust 2021之后才有)](#2.7.1.4.2 原始C字符串字面量(rust 2021之后才有))
        • [2.7.1.5 数字字面量(字面量表达式只能用上面2.7.1.1.7列出的后缀,其他情况后缀随意)](#2.7.1.5 数字字面量(字面量表达式只能用上面2.7.1.1.7列出的后缀,其他情况后缀随意))
          • [2.7.1.5.1 整数字面量](#2.7.1.5.1 整数字面量)
          • [2.7.1.5.2 元组索引](#2.7.1.5.2 元组索引)
          • [2.7.1.5.3 浮点数字面量](#2.7.1.5.3 浮点数字面量)
      • [2.7.2 生命周期与循环标签](#2.7.2 生命周期与循环标签)
      • [2.7.3 标点符号](#2.7.3 标点符号)
      • [2.7.4 分隔符](#2.7.4 分隔符)
      • [2.7.5 保留token](#2.7.5 保留token)
        • [2.7.5.1 保留前缀](#2.7.5.1 保留前缀)
        • [2.7.5.2 保留守卫](#2.7.5.2 保留守卫)
  • 参考

0. 引言

  

  文法搞定的是拆分成token,拆分后的token合法与否归语法管

  

  无法讲清楚的Rust参考(必须阅读后面的内容)

  

  尊重IDE和编译器给你的报错

  

  不要将LF替换为\n,LF指的是物理换行符本身

1. 记法

  记法这一部分主要讲的是定义用来描述Rust语法的符号系统。

1.1 语法

  词法分析器和语法片段使用了以下记法:

记法 示例 含义
大写字母 KW_IF, INTEGER_LITERAL 由词法分析器生成的标记
斜体驼峰 LetStatment , Item 语法产生式
字符串 x, while, * 精确匹配的字符串
x ? \text{x}^? x? pub ? \text{pub}^? pub? 可选项
x ∗ \text{x}^* x∗ OuterAttribute ∗ \text{OuterAttribute}^* OuterAttribute∗ 0个或多个x
x + \text{x}^+ x+ MacroMatch + \text{MacroMatch}^+ MacroMatch+ 1个或多个x
x a . . b \text{x}^{\text{a}..\text{b}} xa..b HEX_DIGIT 1..6 \text{HEX\_DIGIT}^{1..6} HEX_DIGIT1..6 重复a到b次(不含b)
x a . . = b \text{x}^{\text{a}..=\text{b}} xa..=b HEX_DIGIT 1.. = 5 \text{HEX\_DIGIT}^{1..=5} HEX_DIGIT1..=5 重复a到b次(含b)
x n : a . . = b \text{x}^{\text{n}:\text{a}..=\text{b}} xn:a..=b # n : 1.. = 255 \text{\#}^{\text{n}:1..=255} #n:1..=255 重复a到b次(含b),并将重复次数绑定到名称n
x n \text{x}^{\text{n}} xn # n \text{\#}^{\text{n}} #n x重复次数为由先前带标签的重复所绑定到n的值
规则1 规则2 fn Name Paramters 按顺序排列的规则序列
| u8 | u16, Block | Item 二选一(或多个中的一个)
! !COMMENT 当后接表达式不匹配时匹配成功,不消耗任何输入
\[\] b B 匹配列出任意一个字符
- a-z 匹配范围内的任意一个字符
~\[\] ~b B 匹配出列出字符外的任意字符
~字符串 ~\n, ~*/ 匹配除该序列外的任意字符
() ( , P a r a m e t e r ) ? (, Parameter)^? (,Parameter)? 将项分组(将括号内的项视为一个整体)
^ b' ^ ASCII_FOR_CHAR 后续序列必须匹配,否则无条件失败(^前面的一旦匹配,后面序列必须匹配)
U+xxxx...xxxxxx U+0060 单个Unicode字符
<文本> <any ASCII char except CR> 应匹配内容的英文描述
规则 后缀 \text{规则}_{\text{后缀}} 规则后缀 IDENTIFIER_OR_KEYWORD e x c e p t c r a t e \text{IDENTIFIER\_OR\KEYWORD}{except \ crate} IDENTIFIER_OR_KEYWORDexcept crate 对前一规则的修改
// Comment // Single line comment. 延伸至行尾的注释

2. 词法结构

2.1 输入格式

复制代码
Lexer
CHAR -> [U+0000-U+D7FF U+E000-U+10FFFF]
ASCII -> [U+0000-U+007F]
NUL -> U+0000
EOF -> !CHAR

  CHAR------Rust源文件允许的字符范围,覆盖几乎所有Unicode,排除了U+D800-U+DFFF------UTF-16代理对码点。

  ASCII------CHAR的子集,范围U+0000-U+007F,就是英文字母、数字、常用符号128个字符。

  NUL------U+0000,ASCII的第0个字符空字符。

  EOF------文件结束,定义为当前位置后面不再有任何CHAR。

2.1.1 源文件编码

  每个源文件都被解释为UTF-8编码的Unicode字符序列。如果文件不是有效的UTF-8,则是一个错误。

2.2.2 BOM移除

  如果源文件开头第一个字符是BOM,Rust自动移除!

2.2.3 换行符归一化

  将\r\n替换为\n,只替换一次不递归;单独的\r不处理,当空白字符处理。

2.2.4 shebang移除

  比如#!/usr/bin/env cargo的shebang,是unix操作系统如Linux、macOS的特性。操作系统读到后用cargo执行文件;rustc编译器先移除开头BOM、把\r\n替换为\n、再移出shebang,然后才开始分词编译。

2.2.5 分词

  随后将所得字符序列转换为token。转换方式见本节词法结构。

  标准库的include!宏对它读取的文件应用:BOM移除、换行符归一化、include!宏在项外面移出shebang,在表达式\语句时不移除shebang。include_str!宏和include_bytes!宏不做这些引用。

2.2 Whitespace

复制代码
Lexer
WHITESPACE -> 
		U+0009		// 水平制表符\t
	|	U+000A 		// 换行符\n
	|	U+000B 		// 垂直制表符
	|	U+000C 		// 换页符
	| 	U+000D		// 回车符\r
	| 	U+0020		// 空格
	| 	U+0085		// 下一行
	| 	U+200E		// 从左至右标记
	| 	U+200F		// 从右至左标记
	| 	U+2028		// 行分隔符
	| 	U+2029		// 段分隔符

TAB -> U+0009	// 水平制表符\t
LF   -> U+000A 	// 换行符\n
CR  -> U+000D 	// 回车符\r

  Rust遵循Unicode官方定义的Pattern_White_Space属性,Rust定义空白字符是这些字符组成的非空字符串。

  Rust是一种自由格式语言,意味着所有形式的空白字符仅在文法中起分隔token的作用,不具备语义意义。

  如果将每个空白元素替换为任何其他合法的空白元素,Rust程序的含义保持不变。

2.3 Comments

复制代码
Lexer
COMMENT ->
		LINE_COMMENT
	|	INNER_LINE_DOC
	|	OUTER_LINE_DOC
	| 	INNER_BLOCK_DOC
	|	OUTER_BLOCK_DOC
	|	BLOCK_COMMENT

LINE_COMMENT ->
		// ( ~[/ ! LF] | // ) ~[LF]*
	|	// EOF
	| 	// 紧接LF

BLOCK_COMMENT ->
	/* ^
	( BLOCK_COMMENT_OR_DOC | (!*/ CHAR) )*
	*/

INNER_LINE_DOC ->
	//! ^ LINE_DOC_COMMENT_CONTENT ( LF | EOF )

LINE_DOC_COMMENT_CONTENT ->
	( !CR ~[LF] )*

INNER_BLOCK_DOC ->
	/*! ^ ( BLOCK_COMMENT_OR_DOC | BLOCK_CHAR )* */

OUTER_LINE_DOC ->
	/// ^ LINE_DOC_COMMENT_CONTENT ( LF | EOF )

OUTER_BLOCK_DOC ->
	/** ![* /]
	  ^
	  ( ~* | BLOCK_COMMENT_OR_DOC )
	  ( BLOCK_COMMENT_OR_DOC | BLOCK_CHAR )*
	  */

BLOCK_CHAR ->
	( !( */ | CR ) CHAR )

BLOCK_COMMENT_OR_DOC ->
		INNER_BLOCK_DOC
	|	OUTER_BLOCK_DOC
	|	BLOCK_COMMENT

  LINE_COMMENT的第一行匹配有内容的注释:

  必须//开头,然后要么紧跟一个非/、非!、非LF的字符,要么紧跟字面的//(即 ////),后续零个或多个非LF字符,直到遇到LF。

  LINE_COMMENT的第二行匹配文件末尾的无换行注释。

  LINE_COMMENT的第三行匹配//后面一个LF,也就是空注释。

  LINE_DOC_COMMENT_CONTENT匹配零个或多个既不是CR也不是LF的字符;遇到CR会直接导致失败。

  INNER_LINE_DOC必须以//!开头,硬切断后跟零个或多个既非CR也非LF的字符(遇CR直接失败),以LF或EOF结尾。

  OUTER_LINE_DOC必须以///开头,硬切断后跟零个或多个既非CR也非LR的字符(遇CR直接失败),以LF或EOF结尾。

  BLOCK_CHAR匹配一个既非*/也非CR的CHAR。

  BLOCK_COMMENT必须以/*开头,在硬切断后,跟零个或多个循环,每次循环二选一:要么匹配一个非*/的CHAR,要么匹配一个以/*开头的子块(BLOCK_COMMENT/INNER_BLOCK_DOC/OUTER_BLOCK_DOC)。两者在同一个循环里并列交替出现,以*/结尾。

  INNER_BLOCK_DOC必须以/*!开头,在硬切断后,跟零个或多个循环,每次循环二选一:要么匹配一个既非*/也非CR的CHAR,要么匹配一个以/*开头的子块(BLOCK_COMMENT/INNER_BLOCK_DOC/OUTER_BLOCK_DOC)。两者在同一个循环里并列交替出现,以*/结尾。

  OUTER_BLOCK_DOC必须以/**开头,后面不能紧跟*或/,在硬切断后,先匹配一次非*的CHAR或以/*开头的子块,再跟零个或多个以/开头的子块或既非*/也非CR的CHAR,以/结尾。

2.3.1 非文档注释

  注释遵循通用的C++风格------行注释//和块注释/* ... */。支持块注释嵌套。

  非文档注释被解释为一种空白字符。

2.3.2 文档注释

  以恰好三个斜杠///开头的行文档注释,以及块文档注释/** ... */,两者都是外部文档注释,被解释为doc属性的特殊语法(rustdoc那里再详细展开)。

  也就是说,它们等价于在注释体周围写#doc="...",即/// Foo变成#doc=" Foo",/** Bar */变成#doc=" Bar "。因此它们必须出现在接受外部属性的某个东西之前(作用于项)。

  以//!开头的行注释和/*! ... */块注释是文档注释,它们应用于注释所在的父级,而非随后跟随的项(作用于模块/crate)。

  也就是说,它们等价于在注释体周围写#!doc="..."。//!注释通常用于为占据一个源文件的模块编写文档。

  CR不允许出现在文档注释中。

2.3.3 示例

rust 复制代码
//! 一个文档注释,应用于本 crate 的隐式匿名模块

pub mod outer_module {
    //!  - 内部行文档注释
    //!! - 仍然是内部行文档注释(但以感叹号开头)

    /*!  - 内部块文档注释 */
    /*!! - 仍然是内部块文档注释(但以感叹号开头) */

    //   - 只是普通注释
    ///  - 外部行文档注释(恰好 3 个斜杠)
    //// - 只是普通注释

    /*   - 只是普通注释 */
    /**  - 外部块文档注释(恰好 2 个星号) */
    /*** - 只是普通注释 */

    pub mod inner_module {}

    pub mod nested_comments {
        /* 在 Rust 中 /* 我们可以 /* 嵌套注释 */ */ */

        // 三种块注释都可以包含或嵌套在其他任意类型中:

        /*   /* */  /** */  /*! */  */
        /*!  /* */  /** */  /*! */  */
        /**  /* */  /** */  /*! */  */
        pub mod dummy_item {}
    }

    pub mod degenerate_cases {
        // 空的内部行文档注释
        //!

        // 空的内部块文档注释
        /*!*/

        // 空的行注释
        //

        // 空的外部行文档注释
        ///

        // 空的块注释
        /**/

        pub mod dummy_item {}

        // 空的 2 星号块不是文档块,它是块注释
        /***/
    }

    /* 下面的不被允许,因为外部文档注释
       要求有一个接收该文档的项 */

    /// 我的项在哪?
}

2.4 Shebang

  Shebang是一个可选行,通常用于类Unix系统中,执行该文件的解释器。

复制代码
Lexer
SHEBANG ->
	#! !( ( WHITESPACE | LINE_COMMENT | BLOCK_COMMENT )* [ )
	~LF* ( LF | EOF )

  SHEBANG必须以#!开头,后面不能出现零个或多个的空白字符/行注释(//)或块注释,紧跟零个或多个非LF字符,以LF或EOF结尾。

  SHEBANG出现文件开头,或BOM之后。

2.4.1 示例

  目前1.97.1stable无法在Linux上运行下面的例子,必须启用nightly工具链。

rust 复制代码
#!/usr/bin/env cargo 

fn main() {
	println!("Hello!");
}

2.5 Keywords(具体关键词后面会讲)

  Rust区分关键词为三类:strict,reserved,weak。

2.5.1 Strict Keywords

  这些关键字只能在正确的上下文中使用。它们不能用作以下名称:

  items

  Variables and function parameters

  Fields and variants

  Type parameters

  Lifetime parameters or loop labels

  Macros or attributes

  Macro placeholders

  Crates

  以下是所有版本包含的关键词:

  _

  as

  async(2018版本新增)

  await(2018版本新增)

  break

  const

  continue

  crate

  dyn(2018版本新增)

  else

  enum

  extern

  false

  fn

  for

  if

  impl

  in

  let

  loop

  match

  mod

  move

  mut

  pub

  ref

  return

  self

  Self

  static

  struct

  super

  trait

  true

  type

  unsafe

  use

  where

  while

2.5.2 Reserved Keywords

  这些关键字目前尚未使用,但保留供将来使用。它们与strict keywords有相同的限制。这样做的目的是通过禁止当前程序使用这些关键字,使其与未来版本的Rust保持前向兼容。

  abstract

  become

  box

  do

  final

  gen(2024版本新增)

  macro

  override

  priv

  try(2018版本新增)

  typeof

  unsized

  virtual

  yield

2.5.3 Weak Keywords

  这些关键字仅在特定上下文中具有特殊含义。

  'static(用于表示静态生命周期,不能用作泛型生命周期或循环标签)

  macro_rules(用于创建声明宏)

  raw(用于裸指针)

  safe(用于函数和静态变量,在external blocks中有意义)

  union(声明联合体,仅在联合体声明中作为关键字)

2.5.4 附录

  在2015版本中,dyn在类型位置后跟不以::或<开头的路径、生命周期、问号、for关键字或左括号时作为关键字。

2.6 Identifiers

  XID_Start是Unicode标准定义的作为编程语言标识符开头的字符------字母(广义)。XID_Continue是Unicode标准定义的作为编程语言标识符后续的字符------XID_Start对应的字符、数字。

  IDENTIFIER_OR_KEYWORD以XID_Start对应的字符或下划线开头,后续跟零个或多个XID_Continue对应的字符。

  RAW_IDENTIFIER必须以r#开头,紧跟XID_Start或_,后面继续跟零个或多个XID_Continue。

  NON_KEYWORD_IDENTIFIER是除了上面提及的Strict和Reserve关键词之外的IDENTIFIER_OR_KEYWORD。

  IDENTIFIER是NON_KEYWORD_IDENTIFIER或RAW_IDENTIFIER。

  RESERVED_RAW_IDENTIFIER必须以r#开头,紧跟_或crate或self或Self或super,不能是XID_Continue(遇XID_Continue直接失败)。

2.6.1 标识符具体要求

  标识符遵循Unicode17.0标准的附录#31,并附加下文的补充要求。一些标识符样例:

  foo

  _identifier

  r#true

  Москва

  东京

  Rust从Unicode标准附录#31中采用的配置为:

  Start := XID_Start,加下划线

  Continue := XID_Continue

  Medial := 空

  以下划线开头的标识符通常用于表示有意不使用的标识符,消除编译器的未使用警告。

  标识符不能是Strict和Reserved关键字,除非使用原始标识符中描述的r#前缀。

  标识符中不允许出现零宽非连接符(ZWNJ U+200C)和零宽连接符(ZWJ U+200D)字符。

  在以下情况下,标识符被限制为XID_Start和XID_Continue的ASCII子集:

  extern crate声明(AsClause除外)

  在路径中引用的外部crate名称

  从文件系统加载的模块名称(没有path属性的情况下)

  带有no_mangle属性的项

  外部块中项的名称

2.6.2 归一化(没啥意义)

  标识符使用Unicode标准附录#15定义的归一化形式C (NFC)进行归一化。两个标识符的NFC形式相等时,它们才相等。

  过程宏和声明宏接收的输入中的标识符是已归一化的。

2.6.3 Raw identifiers

  原始标识符类似于普通标识符,但带有r#前缀。比如r#match,实际标识符名称还是match。

  与普通标识符不同,原始标识符可以除上述RESERVED_RAW_IDENTIFIER中列出的之外的任何Strict或Reserved关键字。

  使用RESERVED_RAW_IDENTIFIER会报错。

2.6.3.1 用处

  r#主要用于"名字改不了"的场景:FFI对接C库时函数名是别人定的(如 fn r#match),跨版本升级时旧代码里已有的标识符变成关键字(如2015版的async在2018版变成关键字),以及从外部schema自动生成代码时字段名来自数据库或协议定义(如 r#type)。日常写代码应该换个名字,r#只是被迫面对关键字冲突时的兜底。

2.7 Tokens

复制代码
Lexer
Token ->
		RESERVED_TOKEN
	|	RAW_IDENTIFIER
	|	CHAR_LITERAL
	|	STRING_LITERAL
	| 	RAW_STRING_LITERAL
	|	BYTE_LITERAL
	| 	BYTE_STRING_LITERAL
	| 	RAW_BYTE_STRING_LITERAL
	| 	C_STRING_LITERAL
	|	RAW_C_STRING_LITERAL
	|	FLOAT_LITERAL
	| 	INTEGER_LITERAL
	|	LIFETIME_TOKEN
	|	PUNCTUATION
	|	IDENTIFIER_OR_KEYWORD

  Token是文法中由正则(非递归)语言定义的原始产生式。Rust源码可被分解为以下几类token:

  Keywords

  Identifiers

  Literals

  Lifetimes

  Punctuation

  Delimiters

2.7.1 Literals

  字面量是用于字面量表达式literal expr的token。

2.7.1.1 示例
2.7.1.1.1 字符与字符串
示例 #数量 字符 支持的转义(下面提及)
字符 'H' 0 全部Unicode 引号、ASCII、Unicode
字符串 "hello" 0 全部Unicode 引号、ASCII、Unicode
原始字符串 r#"hello"# <256 全部Unicode
字节 b'H' 0 全部ASCII 引号、字节
字节串 b"hello" 0 全部ASCII 引号、字节
原始字节串 br#"hello"# <256 全部ASCII
C字符串 c"hello" 0 全部Unicode 引号、字节、Unicode
原始C字符串 cr#"hello"# <256 全部Unicode

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

2.7.1.1.2 ASCII转义序列
名称
\x41 7位字符代码(恰好2位十六进制,上限0x7F)
\n 换行
\r 回车
\t 制表符
\ 反斜杠
\0 空字符
2.7.1.1.3 字节转义序列
名称
\x7F 8位字符代码(恰好2位十六进制)
\n 换行
\r 回车
\t 制表符
\ 反斜杠
\0 空字符
2.7.1.1.4 Unicode转义序列
名称
\u{7FFF} 24位Unicode字符代码(最多6位十六进制)
2.7.1.1.5 引号转义序列
名称
\' 单引号
\" 双引号
2.7.1.1.6 数字
数字字面量 示例 指数
十进制整数 98_222
十六进制整数 0xff
八进制整数 0o77
二进制整数 0b1111_0000
浮点数 123.0E+77 可选

  所有数字字面量允许_作为可视分隔符:1_234.0E+18f64。

2.7.1.1.7 后缀(这里举了声明宏怎么处理后缀,但是宏模式语法又提及)

  后缀是紧跟在字面量主体部分之后的字符序列,形式与非原始标识符或关键字相同。

  SUFFIX要么是_开头硬截断后跟一个或多个XID_Continue,要么是XID_Start开头后跟零个或多个XID_Continue。

  任何种类的字面量带任何后缀都作为合法token。

  带任意后缀的字面量token可以传递给宏而不产生错误。宏本身决定如何解释此类token以及是否产生错误。

  下面blackhole_lit!宏的literal片段说明符匹配带任意后缀的字面量token。下面blackhole!宏中tt到底指的是什么,在这里不好解释。

rust 复制代码
macro_rules! blackhole { ($tt:tt) => () }
macro_rules! blackhole_lit { ($l:literal) => () }

fn main() {
	blackhole!("string"suffix);
	blackhole_lit!(1suffix);
}

  但是,被解释为字面量表达式(比如let语句)或模式的字面量token上的后缀受限。非数字字面量拒绝任何后缀,数字字面量token仅接受下面的后缀。

  整数:u8,i8,u16,i16,u32,i32,u64,i64,u128,i128,usize,isize。

  浮点数:f32,f64。

rust 复制代码
fn main() {
	let y = 32u256;
	let x = "string"suffix;
}
2.7.1.2 字符与字符串字面量(字面量表达式不能有后缀,其他情况可以有后缀)
2.7.1.2.1 字符字面量

  QUOTE_ESCAPE是\'或\"。

  ASCII_ESCAPE(2.7.1.1.2的ASCII转义序列)是\x跟两个数字,第一个是八进制数字,第二个是十六进制数字。

  UNICODE_ESCAPE(2.7.1.1.4的Unicode转义序列)以\u{开头,后面跟一到六个序列,每个序列是十六进制数字加零个或多个下划线。此外这一到六个序列表示的必须是合法的Unicode标量值。

rust 复制代码
fn main() {
	// 神完了,谁闲的没事干写下划线?
	let c = '\u{A_________F_______F_F}';
	println!("{c}");
}

  CHAR_LITERAL以'开头,中间内容要么是'、\、LF、CR、TAB以外的任意字符,要么是\'或\",要么是\n或\r或\t或\\或\0或\x加八进制数字加十六进制数字,要么是\u{加一到六个序列(每个序列是十六进制数字加零个或多个下划线,整体构成合法Unicode标量值),然后以'结尾,最后跟可选的SUFFIX。

2.7.1.2.2 字符串字面量

  STRING_CONTINUE(字符串续行)以\开头,紧跟LF。

rust 复制代码
fn main() {
	let s = "abc\
	efg\
	hij";
	println!("{s}");
}

  STRING_LITERAL以"开头,中间重复零个或多个以下内容之一:要么是"或\或CR以外的任意字符,要么是\'或\",要么是\n或\r或\t或\或\0或\x加八进制数字加十六进制数字,要么是\u{加一到六个序列(每个序列是十六进制数字加零个或多个下划线,整体构成合法Unicode标量值),要么是字符串续行,然后以"结尾,最后跟可选的SUFFIX。

2.7.1.2.3 原始字符串字面量

  RAW_STRING_CONTENT是零个或多个CR以外的字符(遇到"失败)。

  RAW_STRING_CONTENT_HASHED是零个或多个CR以外的字符(遇到"后跟RAW_STRING_LITERAL中n个#失败)。

  RAW_STRING_LITERAL要么是以r"开头,硬截断后跟零个或多个CR以外的字符(遇到"失败),以"结尾,后面跟一个可选的SUFFIX,要么以r跟1到255个#开头,硬截断后跟"加零个或多个CR以外的字符(遇到"后跟RAW_STRING_LITERAL中n个#失败)再加",以和前面同样数量的#结尾,最后跟一个可选的SUFFIX。

rust 复制代码
fn main() {
    let s = "\x7F\\\u{AFFF}\"\
    abcd";
    // 不能出现",不转义,不能续行
    let s1 = r"\x7F1234\u{AFFF}꿿\
    abcd";
    // 可以直接出现",不转义,不能续行
    let s2 = r##"\x7F""""#1234\u{AFFF}꿿\
    abcd"##;
    println!("{}\n{}\n{}",s, s1, s2);
}
2.7.1.3 字节与字节串字面量(字面量表达式不能有后缀,其他情况可以有后缀)
2.7.1.3.1 字节字面量

  ASCII_FOR_CHAR是ASCII(遇'或\或LF或CR或TAB失败)。

  BYTE_ESCAPE()是\x后跟两个十六进制数字或\n或\r或\t或\\或\0或\'或\"。

  BYTE_LITERAL以b'开头,硬截断后是ASCII(遇'或\或LF或CR或TAB失败)或\x后跟两个十六进制数字或\n或\r或\t或\\或\0或\'或\",以'结尾,后面跟一个可选的SUFFIX。

2.7.1.3.2 字节串字面量

  ASCII_FOR_STRING是ASCII(遇到"或\或CR失败)。

  BYTE_STRING_LITERAL以b"开头,硬截断后重复零个或多个以下内容之一:要么是ASCII(遇到"或\或CR失败),要么是BYTE_ESCAPE,要么是字符串续行,以"结尾,后面跟一个可选的SUFFIX。

rust 复制代码
fn main() {
    let bs = b"\xFF\n\
	abcd";
    println!("{bs:?}");
    let s: String = bs.into_iter().map(|v| *v as char).collect();
    println!("{s}");
}
2.7.1.3.3 原始字节串字面量

  ASCII_FOR_RAW是ASCII(遇到CR失败)。

  RAW_BYTE_STRING_CONTENT是零个或多个ASCII(遇到CR或"失败)。

  RAW_BYTE_STRING_CONTENT_HASHED是零个或多个ASCII(遇到CR或"后跟RAW_BYTE_STRING_LITERAL中n个#失败)。

  RAW_BYTE_STRING_LITERAL要么是以br"开头,硬截断后跟零个或多个ASCII(遇到CR或"失败),以"结尾,后面跟一个可选的SUFFIX,要么是以br后跟1到255个#开头,硬截断后跟"加零个或多个ASCII(遇到CR或"后跟RAW_BYTE_STRING_LITERAL中n个#失败)再加",以和前面数量一样的#结尾,后面跟一个可选的SUFFIX。

rust 复制代码
fn main() {
    let bs = b"\xFF\n\
	abcd";
    // 不能有",不能转义,不能续行
    let bs1 = br"\xFF\n\
    abcd";
    // 不能转义,不能续行
    let bs2 = br##"\xFF#\n""""\
    abcd"##;
    println!("{bs:?}");
    println!("{bs1:?}");
    println!("{bs2:?}");
    let s: String = bs.into_iter().map(|v| *v as char).collect();
    let s1: String = bs1.into_iter().map(|v| *v as char).collect();
    let s2: String = bs2.into_iter().map(|v| *v as char).collect();
    println!("{s}");
    println!("{s1}");
    println!("{s2}");
}
2.7.1.4 C字符串与原始C字符串字面量(字面量表达式不能有后缀,其他情况可以有后缀)
2.7.1.4.1 C字符串字面量(rust 2021之后才有)

  C_STRING_LITERAL以c"开头,硬截断后重复零个或多个以下内容之一:要么是"或\或CR或NUL以外的字符,要么是\0或\x00之外的BYTE_ESCAPE,要么是\u{0}或\u{00}或\u{000}或\u{0000}或\u{00000}或\u{000000}以外的UNICODE_ESCAPE,要么是字符串续行,以"结尾,后面跟一个可选的SUFFIX。

2.7.1.4.2 原始C字符串字面量(rust 2021之后才有)

  RAW_C_STRING_CONTENT是零个或多个既不是CR也不是NUL的字符(遇到"失败)。

  RAW_C_STRING_CONTENT_HASHED是零个或多个既不是CR也不是NUL的字符(遇到"后跟RAW_C_STRING_LITERAL中n个#失败)。

  RAW_C_STRING_LITERAL要么是以cr"开头,硬截断后跟零个或多个既不是CR也不是NUL的字符(遇到"失败),以"结尾,后面跟一个可选的SUFFIX,要么以cr后跟1到255个#开头,硬截断后跟"加零个或多个既不是CR也不是NUL的字符(遇到"后跟RAW_C_STRING_LITERAL中n个#失败)加",以和前面数量一样的#结尾,后面跟一个可选的SUFFIX。

rust 复制代码
fn main() {
    let cs = c"\x01\u{FFFD}\
    abcd";
    // 不能有",不识别转义,不续行
    let cs1 = cr"\x01\u{FFFD}\
    abcd";
    // 只能出现",不识别转义,不续行
    let cs2 = cr##""\x01\u{FFFD}#\
    abcd"##;
    println!("{cs:?}");
    println!("{cs1:?}");
    println!("{cs2:?}");
    let s: String = cs.to_bytes_with_nul().into_iter().map(|v| *v as char).collect();
    let s1: String = cs1.to_bytes_with_nul().into_iter().map(|v| *v as char).collect();
    let s2: String = cs2.to_bytes_with_nul().into_iter().map(|v| *v as char).collect();
    println!("{s}");
    println!("{s1}");
    println!("{s2}");
}
2.7.1.5 数字字面量(字面量表达式只能用上面2.7.1.1.7列出的后缀,其他情况后缀随意)
2.7.1.5.1 整数字面量

  BIN_DIGIT是0、1。

  OCT_DIGIT是0、1、2、3、4、5、6、7。

  DEC_DIGIT是0、1、2、3、4、5、6、7、8、9.

  HEX_DIGIT是0、1、2、3、4、5、6、7、8、9、a、b、c、d、e、f、A、B、C、D、E、F。

  DEC_LITERAL以DEC_DIGIT开头,后面重复零个或多个以下内容之一:要么是DEC_DIGIT,要么是下划线。

  BIN_LITERAL以0b开头,硬截断后跟零个或多个下划线加BIN_DIGIT,后面重复零个或多个以下内容之一:要么是BIN_DIGIT,要么是下划线。接下来一个字符如果遇到e或E或2或3或4或5或6或7或8或9直接失败。

  OCT_LITERAL以0o开头,硬截断后跟零个或多个下划线加OCT_DIGIT,后面重复零个或多个以下内容之一:要么是OCT_DIGIT,要么是下划线。接下来一个字符如果遇到e或E或8或9直接失败。

  HEX_DIGIT以0x开头,硬截断后跟零个或多个下划线加HEX_DIGIT,后面重复零个或多个以下内容之一:要么是HEX_DIGIT,要么是下划线。

  RESERVED_FLOAT以.开头,下一个字符遇到.或_或XID_Start失败。

  INTEGER_LITERAL要么以BIN_LITERAL开头,要么以OCT_LITERAL开头,要么以HEX_LITERAL开头,要么以DEC_LITERAL开头,硬截断后如果遇到RESERVED_FLOAT失败,后面跟一个可选的SUFFIX。

2.7.1.5.2 元组索引
复制代码
Lexer
TUPLE_INDEX -> DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL

  元组索引用于引用元组、元组结构体和元组枚举变体的字段。

  元组索引直接与字面量token比较,元组索引从0开始,每个后续索引递增1。只有十进制整型字面量会匹配,不能有前导零、后缀和下划线。

rust 复制代码
fn main() {
    let example = ("dog", "cat", "horse");
    let dog = example.0;
    let cat = example.1;
    let cat = example.01;
    let cat = example.1usize;
    let dog = example.0b01;
    let dog = example.0o1;
    let dog = example.0x1;
}
2.7.1.5.3 浮点数字面量

  FLOAT_EXPONENT要么以e开头,要么以E开头,硬截断后跟一个可选的+或-加零个或多个下划线加DEC_DIGIT,后面重复零个或多个以下内容之一:要么是DEC_DIGIT,要么是下划线。

  FLOAT_LITERAL要么以DEC_LITERAL开头,跟一个可选的序列:.加DEC_LITERAL,以FLOAT_EXPONENT结尾,后面跟一个可选的SUFFIX;要么以DEC_LITERAL开头,跟一个.加DEC_LITERAL,后面跟一个可选的SUFFIX;要么以DEC_LITERAL开头,跟一个.(遇到.或_或XID_Start失败)。

rust 复制代码
fn main() {
    let f = 2.;
    let f1 = 2.f64;
}

2.7.2 生命周期与循环标签

复制代码
Lexer
LIFETIME_TOKEN ->
		RAW_LIFETIME
	|	' IDENTIFIER_OR_KEYWORD !'
LIFETIME_OR_LABEL ->
		RAW_LIFETIME
	| ' NON_KEYWORD_IDENTIFIER !'
RAW_LIFETIME ->
	'r# ^ IDENTIFIER_OR_KEYWORD !'
RESERVED_RAW_LIFETIME -> 'r# ( _ | crate | self | Self | super ) !( ' | XID_Continue )

  RAW_LIFETIME以'r#开头,硬截断后跟IDENTIFIER_OR_KEYWORD,下一个字符遇到'失败。

  RESERVED_RAW_LIFETIME以'r#开头,后面跟_或crate或self或Self或super,下一个字符遇到'或XID_Continue失败。

  LIFETIME_OR_LABAL要么是RAW_LIFETIME,要么是以'开头,跟NON_KEYWORD_IDENTIFIER,下一个字符遇到'失败。

  LIFETIME_TOKEN要么是RAW_LIFETIME,要么是以'开头,跟一个IDENTIFIER_OR_KEYWORD,下一个字符遇到'失败。

  生命周期参数和循环标签使用LIFETIME_OR_LABEL。原始生命周期中r#不是实际生命周期的一部分。使用RESERVED_RAW_LIFETIME_TOKEN会报错。

2.7.3 标点符号

复制代码
Lexer
PUNCTUATION -> 
		... | ..= | <<= | >>= | != | %= | && | &= | *= | += | j -= | -> | ..
	| /= | :: | <- | << | <= | == | => | >= | >> | ^= | |= | || | ! | # | $ 
	| % | & | ( | ) | * | + | , | - | . | / | : | ; | < | = | > | ? | @ | [ 
	| ] | ^ | { | | | } | ~

2.7.4 分隔符

  括号标点符号用于文法的各个部分。左括号必须始终与右括号匹配。括号及其中的token在宏中称为token树(这里宏中token数的定义不完整)。

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

2.7.5 保留token

  几种token形式被保留供将来使用或避免混淆。源输入中包含这些会报错。

复制代码
Lexer
RSERVED_TOKEN ->
		RESERVED_GUARDED_STRING_LITERAL
	|	RESERVED_POUNDS
	|	RESERVED_RAW_IDENTIFIER
	| 	RESERVED_RAW_LIFETIME
	|	RESERVED_TOKEN_DOUBLE_QUOTE
	|	RESERVED_TOKEN_LIFETIME
	|	RESERVED_TOKEN_POUND
	|	RESERVED_TOKEN_SINGLE_QUOTE
2.7.5.1 保留前缀

  RESERVED_TOKEN_DOUBLE_QUOTE是以b或c或r或br或cr以外的IDENTIFIER_OR_KEYWORD加"。

  RESERVED_TOKEN_SINGLE_QUOTE是除b以外的IDENTIFIER_OR_KEYWORD加'。

  RESERVED_TOKEN_POUND是除r或br或cr以外的IDENTIFIER_OR_KEYWORD加#。

  RESERVED_TOKEN_LIFETIME以'开头,跟除r以外的IDENTIFIER_OR_KEYWORD加#。

2.7.5.2 保留守卫

  RESERVED_GUARDED_STRING_LITERAL以一个或多个#开头,跟STRING_LITERAL(正常的字符串)。

  RESERVED_POUNDS是2个及以上的#。

参考

1、记法

2、词法结构

相关推荐
SomeB1oody10 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
红尘散仙13 小时前
TypeScript WIT Guest:类型约束与 ComponentizeJS 工程化
rust·typescript·webassembly
小灰灰搞电子13 小时前
Rust+Slint 实现的“DNA双螺旋”加载动画源码分享
后端·rust·slint·加载动画
红尘散仙13 小时前
从 dsh 的插件系统出发:为什么我要探索 WIT 与 Wasm Component Model
rust·typescript·webassembly
红尘散仙13 小时前
从 WIT 到 Wasmtime:构建 Rust Component 工程
rust·typescript·webassembly
编码浪子19 小时前
Rust 智能指针深度实战:Box/Rc/Arc/RefCell 选型决策与内存管理避坑
开发语言·后端·rust
对象存储与RustFS2 天前
用 rclone 把现有 S3/MinIO 数据同步到 RustFS
后端·rust·开源
2501_915918412 天前
Rust 程序抓包解密,rustls 不认系统证书的几种办法
开发语言·后端·网络协议·ios·adb·https·rust
学心理学的程序员2 天前
anydoc:Firecrawl 出的 Rust 文档转 Markdown,4ms 转换、14 种格式干翻 MarkItDown
开发语言·后端·rust·开源·firecrawl·claude code·anydoc