Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

简介

本书是 Rust 编程语言的主要参考文档。

Note

关于本书中已知的错误和遗漏,请参阅我们的 GitHub issues。如果您发现编译器行为与本文不一致的情况,请提交 issue 以便我们考虑哪个是正确的。

Rust 版本发布

Rust 每六周发布一次新版本。 该语言的第一个稳定版本是 Rust 1.0.0,随后是 Rust 1.1.0,依此类推。 工具(rustccargo 等)和文档([标准库]、本书等)随语言版本一起发布。

本书的最新版本(与最新 Rust 版本匹配)始终可以在 https://doc.rust-lang.org/reference/ 找到。 之前的版本可以通过在 “reference” 目录之前添加 Rust 版本来找到。 例如,Rust 1.49.0 的参考文档位于 https://doc.rust-lang.org/1.49.0/reference/

参考文档不是什么

本书不作为该语言的入门教程。 假定读者已经具备该语言的背景知识。 另外有一本书籍可以帮助获取这种背景知识。

本书也不作为语言发行版中包含的[标准库]的参考文档。 这些库通过从其源代码中提取文档属性来单独记录。 许多您可能认为是语言特性的功能在 Rust 中是库特性,所以您要查找的内容可能在那里,而不是在这里。

同样,本书通常不记录 rustc 作为工具或 Cargo 的具体细节。 rustc 有自己的书籍。 Cargo 有一本书籍,其中包含一个参考文档。 有几页(如链接)仍然描述了 rustc 的工作方式。

本书也仅作为稳定版 Rust 中可用功能的参考。 关于正在开发的不稳定功能,请参阅不稳定之书

Rust 编译器(包括 rustc)会执行优化。 参考文档不指定允许或禁止哪些优化。 相反,将编译后的程序视为一个黑盒。 您只能通过运行它、输入数据并观察其输出来探测。 以这种方式发生的一切都必须符合参考文档的说明。

如何使用本书

本书不假定您是按顺序阅读本书。 每章通常可以独立阅读,但会交叉链接到其他章节,以获取它们引用但未讨论的语言方面。

阅读本文档有两种主要方式。

第一种是回答特定问题。 如果您知道哪一章回答了该问题,可以直接跳转到目录中的那一章。 否则,您可以按 s 或单击顶部栏上的放大镜来搜索与问题相关的关键字。 例如,假设您想知道在 let 语句中创建的临时值何时被丢弃。 如果您不知道临时值的生命周期是在表达式章节中定义的,您可以搜索 “temporary let”,第一个搜索结果将带您到该部分。

第二种是总体上提高您对语言某个方面的了解。 在这种情况下,只需浏览目录直到看到您想了解更多的内容,然后开始阅读。 如果某个链接看起来很有趣,请单击它,并阅读该部分。

话虽如此,阅读本书没有错误的方式。请以您认为最有帮助的方式阅读。

约定

与所有技术书籍一样,本书在显示信息方面有一定的约定。 这些约定在此处记录。

  • 定义术语的语句将该术语用斜体表示。 当该术语在该章节之外使用时,通常是链接到包含此定义的部分。

    示例术语是术语定义的一个示例。

  • 主要文本描述最新的稳定版本。与之前版本的区别在版本块中分开:

    2018 Edition differences

    在 2018 版本之前,行为是这样的。从 2018 版本开始,行为是那样的。

  • 包含有关书籍状态的有用信息或指出有用但大多超出范围的信息的注释在注释块中。

    Note

    这是一个示例注释。

  • 示例块展示了一个演示某些规则或指出某些有趣方面的示例。某些示例可能有隐藏行,可以通过将鼠标悬停或点击示例时出现的眼睛图标来查看。

    Example

    这是一个代码示例。

    #![allow(unused)]
    fn main() {
    println!("hello world");
    }
  • 显示语言中不健全行为或可能令人困惑的语言特性交互的警告在特殊警告框中。

    Warning

    这是一个示例警告。

  • 文本中内联的代码片段在 <code> 标签内。

    较长的代码示例在语法高亮框中,右上角有用于复制、执行和显示隐藏行的控件。

    // This is a hidden line.
    fn main() {
        println!("This is a code example");
    }

    除非另有说明,所有示例都是为最新版本编写的。

  • 语法和词法产物在符号约定章节中描述。

  • 规则标识符出现在每个语言规则之前,用方括号括起来。这些标识符提供了一种引用和链接到语言中特定规则的方式(例如)。规则标识符使用句点将部分从最一般到最具体分开(例如 destructors.scope.nesting.function-body)。在窄屏幕上,规则名称将折叠显示为 [*]

    可以单击规则名称以链接到该规则。

    Warning

    规则的组织目前仍在变化中。暂时来说,这些标识符名称在版本之间不稳定,如果更改了,指向这些规则的链接可能会失效。我们打算在组织稳定后稳定这些名称,以便指向规则名称的链接不会在版本之间中断。

  • 有关联测试的规则将在其下方包含一个 Tests 链接(在窄屏幕上,链接为 [T])。单击该链接将弹出一个测试列表,可以单击查看测试。例如,请参阅 input.encoding.utf8

    将规则链接到测试是一项正在进行的工作。有关概述,请参阅[测试摘要][Test summary]章节。

贡献

我们欢迎各种形式的贡献。

您可以通过打开 issue 或向 Rust 参考文档仓库 发送 pull request 来为本书做出贡献。 如果本书没有回答您的问题,并且您认为其答案在其范围内,请不要犹豫提交 issue或在 Zulip 上的 t-lang/doc 流中询问。 了解人们最常使用本书做什么有助于我们将注意力集中在使这些部分尽可能好上。 当然,如果您发现任何错误或非规范但未特别指出的内容,也请提交 issue

符号约定

语法

以下符号由 词法分析器(Lexer)和 语法(Syntax)语法片段使用:

符号示例含义
CAPITALKW_IF, INTEGER_LITERAL由词法分析器产生的词法单元
ItalicCamelCaseLetStatement, Item语法产物
stringx, while, *精确的字符
x?pub?可选项
x*OuterAttribute*0 个或多个 x
x+MacroMatch+1 个或多个 x
xa..bHEX_DIGIT1..6a 到 b 次重复 x,不包含 b
xa..=bHEX_DIGIT1..=5a 到 b 次重复 x,包含 b
xn:a..=b#n:1..=255a 到 b 次重复 x(包含 b),计数绑定到名称 n
xn#nx 重复绑定到 n 的次数,由先前标记的重复定义
Rule1 Rule2fn Name Parameters按顺序排列的规则序列
|u8 | u16, Block | Item其中之一
!!COMMENT如果表达式不匹配则匹配,不消耗任何输入
[ ][b B]列出的任意字符
[ - ][a-z]范围内的任意字符
~[ ]~[b B]除列出字符外的任意字符
~string~\n, ~*/除此序列外的任意字符
( )(, Parameter)?分组项
^b' ^ ASCII_FOR_CHAR序列的其余部分必须匹配,否则解析无条件失败([硬切操作符])
U+xxxx..xxxxxxU+0060单个 Unicode 字符
<text><any ASCII char except CR>应匹配内容的英文描述
Rule suffixIDENTIFIER_OR_KEYWORD except crate对前一规则的修改
// Comment.// Single line comment.延伸到行尾的注释。

序列的优先级高于 | 交替。

硬切操作符

语法使用有序交替:解析器从左到右尝试替代方案,并采用第一个匹配的方案。如果某个替代方案在序列中途失败,解析器通常会回溯并尝试下一个替代方案。切操作符(^)会阻止这种情况。一旦序列中 ^ 左侧的所有表达式都已匹配,序列的其余部分必须匹配,否则解析无条件失败。

Mizushima 等人将切操作符引入了解析表达式文法。在 PEG 文献中,软切仅阻止在直接包含的有序选择中回溯 — 外部选择仍然可以恢复。硬切阻止所有经过切点的回溯;失败是确定性的。本文法中使用的 ^ 是硬切。

硬切操作符是必要的,因为 Rust 中的某些词法单元以本身是有效词法单元的前缀开头。例如,c" 开始一个 C 字符串字面量,但单独的 c 是一个有效的标识符。如果没有切,如果 c"\0" 作为 C 字符串字面量词法分析失败(因为 C 字符串中不允许空字节),解析器可能会回溯并将其词法分析为两个词法单元:标识符 c 和字符串字面量 "\0"c" 之后的切 阻止了这种情况 — 一旦识别出开头的分隔符,解析器就无法回溯。同样的推理适用于字节字面量字节字符串字面量原始字符串字面量以及其他以前缀开头的字面量,这些前缀本身是有效的词法单元。

字符串表产物

语法中的某些规则 — 特别是一元运算符二元运算符关键字 — 以简化形式给出:作为可打印字符串的列表。这些情况构成了关于词法单元规则的子集,并假定是由 DFA 驱动的词法分析阶段的结果,该阶段操作所有此类字符串表条目的析取。

当此类字符串以 monospace 字体出现在语法中时,它是对此类字符串表产物的单个成员的隐式引用。有关更多信息,请参阅词法单元

语法可视化

每个语法块下方都有一个按钮,用于切换语法图的显示。方形元素是非终结符规则,圆角矩形是终结符。

词法结构

输入格式

Lexer
CHAR → [U+0000-U+D7FF U+E000-U+10FFFF] // a Unicode scalar value

ASCII → [U+0000-U+007F]

NUL → U+0000

EOF → !CHAR // End of file or input

CHAR U+0000-U+D7FF U+E000-U+10FFFF
ASCII U+0000-U+007F
NUL U+0000
EOF not followed by CHAR

本章描述源文件如何被解释为词法单元序列。

有关程序如何组织到文件中的描述,请参阅包和源文件

源编码

每个源文件被解释为以 UTF-8 编码的 Unicode 字符序列。

如果文件不是有效的 UTF-8,则是错误。

字节顺序标记移除

如果序列中的第一个字符是 U+FEFF字节顺序标记),则将其移除。

CRLF 规范化

每对字符 U+000D (CR) 紧接着 U+000A (LF) 被替换为单个 U+000A (LF)。这发生一次,而不是重复发生,因此在规范化之后,输入中仍然可以存在 U+000D (CR) 紧接着 U+000A (LF)(例如,如果原始输入包含 “CR CR LF LF”)。

字符 U+000D (CR) 的其他出现保留在原处(它们被视为空白)。

Shebang 移除

如果存在 shebang,则将其从输入序列中移除(因此被忽略)。

词法分析

然后,生成的字符序列按照本章其余部分的描述转换为词法单元。

Note

标准库 include! 宏对其读取的文件应用以下转换:

  • 字节顺序标记移除。
  • CRLF 规范化。
  • 在项上下文中调用时移除 shebang(与表达式或语句上下文相反)。

include_str!include_bytes! 宏不应用这些转换。

Shebang

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

Example

#!/usr/bin/env rustx

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

Lexer
SHEBANG
    #! !( ( WHITESPACE | LINE_COMMENT | BLOCK_COMMENT )* [ )
    ~LF* ( LF | EOF )

shebang 以字符 #! 开头,延伸到第一个 U+000A (LF) 或如果没有 LF 则延伸到 EOF。如果 #! 字符后面跟着 [(忽略任何中间的注释空白),则该行不被视为 shebang(以避免与内部属性的歧义)。

shebang 可以直接出现在文件开头或可选的字节顺序标记之后。

关键字

Rust 将关键字分为三类:

严格关键字

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

以下关键字在所有版本中都存在:

  • _
  • as
  • async
  • await
  • break
  • const
  • continue
  • crate
  • dyn
  • 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

2018 Edition differences

以下关键字在 2018 版本中添加:

  • async
  • await
  • dyn

保留关键字

这些关键字尚未使用,但被保留供将来使用。它们与严格关键字具有相同的限制。其背后的原因是通过禁止当前程序使用这些关键字,使当前程序与 Rust 的未来版本向前兼容。

  • abstract
  • become
  • box
  • do
  • final
  • gen
  • macro
  • override
  • priv
  • try
  • typeof
  • unsized
  • virtual
  • yield

2018 Edition differences

try 关键字在 2018 版本中作为保留关键字添加。

2024 Edition differences

gen 关键字在 2024 版本中作为保留关键字添加。

弱关键字

这些关键字仅在某些上下文中具有特殊含义。例如,可以声明名为 union 的变量或方法。

  • 'static
  • macro_rules
  • raw
  • safe
  • union
  • macro_rules 用于创建自定义
  • union 用于声明联合体,仅在联合体声明中使用时才是关键字。
  • 'static 用于静态生命周期,不能用作泛型生命周期参数循环标签

    // error[E0262]: invalid lifetime parameter name: `'static`
    fn invalid_lifetime_parameter<'static>(s: &'static str) -> &'static str { s }
    
  • safe 用于函数和静态变量,在外部块中具有意义。
  • raw 用于原始借用运算符,仅在匹配原始借用运算符形式(如 &raw const expr&raw mut expr)时才是关键字。

2018 Edition differences

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

从 2018 版本开始,dyn 已被提升为严格关键字。

标识符

Lexer
IDENTIFIER_OR_KEYWORD → ( XID_Start | _ ) XID_Continue*

XID_Start<XID_Start defined by Unicode>

XID_Continue<XID_Continue defined by Unicode>

RAW_IDENTIFIERr# IDENTIFIER_OR_KEYWORD

NON_KEYWORD_IDENTIFIERIDENTIFIER_OR_KEYWORDexcept a strict or reserved keyword

IDENTIFIERNON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER

RESERVED_RAW_IDENTIFIER
    r# ( _ | crate | self | Self | super ) !XID_Continue

标识符遵循 Unicode 标准附录 #31 中 Unicode 版本 17.0 的规范,并加上下面描述的附加内容。标识符的一些示例:

  • foo
  • _identifier
  • r#true
  • Москва
  • 東京

从 UAX #31 使用的配置文件是:

Note

以下划线开头的标识符通常用于表示故意未使用的标识符,并将消除 rustc 中的未使用警告。

标识符不能是严格保留关键字,除非使用下面原始标识符中描述的 r# 前缀。

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

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

规范化

标识符使用 Unicode 标准附录 #15 中定义的规范化形式 C (NFC) 进行规范化。如果两个标识符的 NFC 形式相等,则它们相等。

过程声明宏在其输入中接收规范化的标识符。

原始标识符

原始标识符类似于普通标识符,但以 r# 为前缀。(请注意,r# 前缀不作为实际标识符的一部分。)

与普通标识符不同,原始标识符可以是任何严格或保留关键字,除了上面为 RAW_IDENTIFIER 列出的那些。

使用 RESERVED_RAW_IDENTIFIER 词法单元是错误的。

注释

Lexer
COMMENT
      LINE_COMMENT
    | INNER_LINE_DOC
    | OUTER_LINE_DOC
    | INNER_BLOCK_DOC
    | OUTER_BLOCK_DOC
    | BLOCK_COMMENT

LINE_COMMENT
      // ( ~[/ ! LF] | // ) ~LF*
    | // EOF
    | //immediately followed by 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

非文档注释

注释遵循一般的 C++ 风格的行注释(//)和块注释(/* ... */)形式。支持嵌套块注释。

非文档注释被解释为一种空白形式。

文档注释

以恰好三个斜杠(///)开头的行文档注释和块文档注释(/** ... */),都是外部文档注释,被解释为 doc 属性的特殊语法。

也就是说,它们等同于在注释主体周围编写 #[doc="..."],即 /// Foo 变成 #[doc=" Foo"]/** Bar */ 变成 #[doc=" Bar "]。因此,它们必须出现在接受外部属性的内容之前。

//! 开头的行注释和 /*! ... */ 块注释是应用于注释父级而不是后续项的文档注释。

也就是说,它们等同于在注释主体周围编写 #![doc="..."]//! 注释通常用于记录占据源文件的模块。

字符 U+000D (CR) 不允许在文档注释中使用。

Note

按照惯例,文档注释包含 Markdown,正如 rustdoc 所期望的那样。但是,注释语法不尊重任何内部 Markdown。/** `glob = "*/*.rs";` */ 在第一个 */ 处终止注释,剩余的代码会导致语法错误。这与行文档注释相比,稍微限制了块文档注释的内容。

Note

序列 U+000D (CR) 紧接着 U+000A (LF) 之前已被转换为单个 U+000A (LF)。

示例

#![allow(unused)]
fn main() {
//! A doc comment that applies to the implicit anonymous module of this crate

pub mod outer_module {

    //!  - Inner line doc
    //!! - Still an inner line doc (but with a bang at the beginning)

    /*!  - Inner block doc */
    /*!! - Still an inner block doc (but with a bang at the beginning) */

    //   - Only a comment
    ///  - Outer line doc (exactly 3 slashes)
    //// - Only a comment

    /*   - Only a comment */
    /**  - Outer block doc (exactly) 2 asterisks */
    /*** - Only a comment */

    pub mod inner_module {}

    pub mod nested_comments {
        /* In Rust /* we can /* nest comments */ */ */

        // All three types of block comments can contain or be nested inside
        // any other type:

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

    pub mod degenerate_cases {
        // empty inner line doc
        //!

        // empty inner block doc
        /*!*/

        // empty line comment
        //

        // empty outer line doc
        ///

        // empty block comment
        /**/

        pub mod dummy_item {}

        // empty 2-asterisk block isn't a doc block, it is a block comment
        /***/

    }

    /* The next one isn't allowed because outer doc comments
       require an item that will receive the doc */

    /// Where is my item?
  mod boo {}
}
}

空白

Lexer
WHITESPACE
      U+0009 // Horizontal tab, '\t'
    | U+000A // Line feed, '\n'
    | U+000B // Vertical tab
    | U+000C // Form feed
    | U+000D // Carriage return, '\r'
    | U+0020 // Space, ' '
    | U+0085 // Next line
    | U+200E // Left-to-right mark
    | U+200F // Right-to-left mark
    | U+2028 // Line separator
    | U+2029 // Paragraph separator

TAB → U+0009 // Horizontal tab, '\t'

LF → U+000A // Line feed, '\n'

CR → U+000D // Carriage return, '\r'

WHITESPACE U+0009 U+000A U+000B U+000C U+000D U+0020 U+0085 U+200E U+200F U+2028 U+2029
TAB U+0009
LF U+000A
CR U+000D

空白是任何非空字符串,仅包含具有 Pattern_White_Space Unicode 属性的字符。

Rust 是一种“自由形式“语言,这意味着所有形式的空白仅用于在语法中分隔_词法单元_,没有语义意义。

如果每个空白元素被任何其他合法空白元素(如单个空格字符)替换,Rust 程序具有相同的含义。

词法单元

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 源输入可以分解为以下类型的词法单元:

在本文档的语法中,“简单“词法单元以字符串表产物形式给出,并以 monospace 字体显示。

字面量

字面量是用于字面量表达式的词法单元。

示例

字符和字符串

示例# 集合1字符转义
字符'H'0所有 Unicode引号 & ASCII & Unicode
字符串"hello"0所有 Unicode引号 & ASCII & Unicode
原始字符串r#"hello"#<256所有 UnicodeN/A
字节b'H'0所有 ASCII引号 & 字节
字节字符串b"hello"0所有 ASCII引号 & 字节
原始字节字符串br#"hello"#<256所有 ASCIIN/A
C 字符串c"hello"0所有 Unicode引号 & 字节 & Unicode
原始 C 字符串cr#"hello"#<256所有 UnicodeN/A

ASCII 转义

名称
\x417 位字符代码(恰好 2 个十六进制数字,最大 0x7F)
\n换行
\r回车
\t制表符
\\反斜杠
\0空字符

字节转义

名称
\x7F8 位字符代码(恰好 2 个十六进制数字)
\n换行
\r回车
\t制表符
\\反斜杠
\0空字符

Unicode 转义

名称
\u{7FFF}24 位 Unicode 字符代码(最多 6 个十六进制数字)

引号转义

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

数字

数字字面量2示例指数
十进制整数98_222N/A
十六进制整数0xffN/A
八进制整数0o77N/A
二进制整数0b1111_0000N/A
浮点数123.0E+77可选

后缀

后缀是跟在字面量主要部分之后(没有中间空白)的字符序列,形式与非原始标识符或关键字相同。

Lexer
SUFFIX
      _ ^ XID_Continue+
    | XID_Start XID_Continue*

任何类型的字面量(字符串、整数等)带有任何后缀作为词法单元都是有效的。

带有任何后缀的字面量词法单元可以传递给宏而不会产生错误。宏本身将决定如何解释这样的词法单元以及是否产生错误。特别是,通过示例定义的宏的 literal 片段说明符匹配带有任意后缀的字面量词法单元。

#![allow(unused)]
fn main() {
macro_rules! blackhole { ($tt:tt) => () }
macro_rules! blackhole_lit { ($l:literal) => () }

blackhole!("string"suffix); // OK
blackhole_lit!(1suffix); // OK
}

然而,被解释为字面量表达式或模式的字面量词法单元上的后缀是受限的。非数字字面量词法单元上的任何后缀都会被拒绝,数字字面量词法单元只接受以下列表中的后缀。

整数浮点数
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isizef32, f64

字符和字符串字面量

字符字面量

Lexer
CHAR_LITERAL
    '
        ( ~[' \ LF CR TAB] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE )
    ' SUFFIX?

QUOTE_ESCAPE\' | \"

ASCII_ESCAPE
      \x OCT_DIGIT HEX_DIGIT
    | \n | \r | \t | \\ | \0

UNICODE_ESCAPE
    \u{ ( HEX_DIGIT _* )1..=6valid hex char value }3

字符字面量_是包含在两个 U+0027(单引号)字符内的单个 Unicode 字符,U+0027 本身除外,它必须通过前面的 U+005C 字符(\)进行_转义

字符串字面量

Lexer
STRING_LITERAL
    " (
        ~[" \ CR]
      | QUOTE_ESCAPE
      | ASCII_ESCAPE
      | UNICODE_ESCAPE
      | STRING_CONTINUE
    )* " SUFFIX?

STRING_CONTINUE\ LF

字符串字面量_是包含在两个 U+0022(双引号)字符内的任意 Unicode 字符序列,U+0022 本身除外,它必须通过前面的 U+005C 字符(\)进行_转义

换行符(由字符 U+000A (LF) 表示)允许在字符串字面量中使用。字符 U+000D (CR) 不得出现在字符串字面量中。当未转义的 U+005C 字符(\)紧接在换行符之前时,换行符不会出现在词法单元表示的字符串中。有关详细信息,请参阅字符串继续转义

字符转义

在字符或非原始字符串字面量中可以使用一些额外的_转义_。转义以 U+005C\)开头,后跟以下形式之一:

  • 7 位代码点转义_以 U+0078x)开头,后跟恰好两个_十六进制数字,值最大为 0x7F。它表示值等于所提供十六进制值的 ASCII 字符。不允许更高的值,因为它们表示 Unicode 代码点还是字节值是不明确的。
  • 24 位代码点转义_以 U+0075u)开头,后跟最多六个_十六进制数字,用大括号 U+007B{)和 U+007D})括起来。它表示等于所提供十六进制值的 Unicode 代码点。该值必须是有效的 Unicode 标量值。
  • _空白转义_是字符 U+006En)、U+0072r)或 U+0074t)之一,分别表示 Unicode 值 U+000A (LF)、U+000D (CR) 或 U+0009 (HT)。
  • _空转义_是字符 U+00300),表示 Unicode 值 U+0000 (NUL)。
  • _反斜杠转义_是字符 U+005C\),必须进行转义才能表示其本身。

原始字符串字面量

Lexer
RAW_STRING_LITERAL
      r " ^ RAW_STRING_CONTENT " SUFFIX?
    | r #n:1..=255 ^ " RAW_STRING_CONTENT_HASHED " #n SUFFIX?

RAW_STRING_CONTENT → ( !" ~CR )*

RAW_STRING_CONTENT_HASHED → ( !( " #n ) ~CR )*

RAW_STRING_LITERAL r " no backtracking RAW_STRING_CONTENT " SUFFIX r repeat count n at most 254 more times # no backtracking " RAW_STRING_CONTENT_HASHED " repeat exactly n times # SUFFIX
RAW_STRING_CONTENT not followed by " ⚠️ with the exception of CR CHAR
RAW_STRING_CONTENT_HASHED not followed by " repeat exactly n times # ⚠️ with the exception of CR CHAR

原始字符串字面量不处理任何转义。它们以字符 U+0072r)开头,后跟少于 256 个字符 U+0023#)和一个 U+0022(双引号)字符。

_原始字符串体_可以包含除 U+000D (CR) 之外的任何 Unicode 字符序列。它仅由另一个 U+0022(双引号)字符终止,后跟与开头 U+0022(双引号)字符之前相同数量的 U+0023#)字符。

原始字符串体中包含的所有 Unicode 字符都表示它们本身,字符 U+0022(双引号)(除非后跟至少与用于开始原始字符串字面量相同数量的 U+0023#)字符)或 U+005C\)没有任何特殊含义。

字符串字面量示例:

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

字节和字节字符串字面量

字节字面量

Lexer
BYTE_LITERAL
    b' ^ ( ASCII_FOR_CHAR | BYTE_ESCAPE ) ' SUFFIX?

ASCII_FOR_CHAR → ![' \ LF CR TAB] ASCII

BYTE_ESCAPE
      \x HEX_DIGIT HEX_DIGIT
    | \n | \r | \t | \\ | \0 | \' | \"

字节字面量_是单个 ASCII 字符(在 U+0000U+007F 范围内)或单个_转义,前面是字符 U+0062b)和 U+0027(单引号),后面是字符 U+0027。如果字符 U+0027 存在于字面量中,它必须通过前面的 U+005C\)字符进行_转义_。它等同于 u8 无符号 8 位整数_数字字面量_。

字节字符串字面量

Lexer
BYTE_STRING_LITERAL
    b" ^ ( ASCII_FOR_STRING | BYTE_ESCAPE | STRING_CONTINUE )* " SUFFIX?

ASCII_FOR_STRING → ![" \ CR] ASCII

非原始_字节字符串字面量_是 ASCII 字符和_转义_的序列,前面是字符 U+0062b)和 U+0022(双引号),后面是字符 U+0022。如果字符 U+0022 存在于字面量中,它必须通过前面的 U+005C\)字符进行_转义_。或者,字节字符串字面量可以是下面定义的_原始字节字符串字面量_。

换行符(由字符 U+000A (LF) 表示)允许在字节字符串字面量中使用。字符 U+000D (CR) 不得出现在字节字符串字面量中。当未转义的 U+005C 字符(\)紧接在换行符之前时,换行符不会出现在词法单元表示的字符串中。有关详细信息,请参阅字符串继续转义

在字节或非原始字节字符串字面量中可以使用一些额外的_转义_。转义以 U+005C\)开头,后跟以下形式之一:

  • 字节转义_以 U+0078x)开头,后跟恰好两个_十六进制数字。它表示等于所提供十六进制值的字节。
  • _空白转义_是字符 U+006En)、U+0072r)或 U+0074t)之一,分别表示字节值 0x0A (ASCII LF)、0x0D (ASCII CR) 或 0x09 (ASCII HT)。
  • _空转义_是字符 U+00300),表示字节值 0x00 (ASCII NUL)。
  • _反斜杠转义_是字符 U+005C\),必须进行转义才能表示其 ASCII 编码 0x5C

原始字节字符串字面量

Lexer
RAW_BYTE_STRING_LITERAL
      br " ^ RAW_BYTE_STRING_CONTENT " SUFFIX?
    | br #n:1..=255 ^ " RAW_BYTE_STRING_CONTENT_HASHED " #n SUFFIX?

RAW_BYTE_STRING_CONTENT → ( !" ASCII_FOR_RAW )*

RAW_BYTE_STRING_CONTENT_HASHED → ( !( " #n ) ASCII_FOR_RAW )*

ASCII_FOR_RAW → !CR ASCII

RAW_BYTE_STRING_LITERAL br " no backtracking RAW_BYTE_STRING_CONTENT " SUFFIX br repeat count n at most 254 more times # no backtracking " RAW_BYTE_STRING_CONTENT_HASHED " repeat exactly n times # SUFFIX
RAW_BYTE_STRING_CONTENT_HASHED not followed by " repeat exactly n times # ASCII_FOR_RAW

原始字节字符串字面量不处理任何转义。它们以字符 U+0062b)开头,后跟 U+0072r),再后跟少于 256 个字符 U+0023#)和一个 U+0022(双引号)字符。

_原始字符串体_可以包含除 U+000D (CR) 之外的任何 ASCII 字符序列。它仅由另一个 U+0022(双引号)字符终止,后跟与开头 U+0022(双引号)字符之前相同数量的 U+0023#)字符。原始字节字符串字面量不能包含任何非 ASCII 字节。

原始字符串体中包含的所有字符都表示它们的 ASCII 编码,字符 U+0022(双引号)(除非后跟至少与用于开始原始字符串字面量相同数量的 U+0023#)字符)或 U+005C\)没有任何特殊含义。

字节字符串字面量示例:

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

C 字符串和原始 C 字符串字面量

C 字符串字面量

Lexer
C_STRING_LITERAL
    c" ^ (
        ~[" \ CR NUL]
      | BYTE_ESCAPEexcept \0 or \x00
      | UNICODE_ESCAPEexcept \u{0}, \u{00}, …, \u{000000}
      | STRING_CONTINUE
    )* " SUFFIX?

C_STRING_LITERAL c" no backtracking ⚠️ with the exception of " \ CR NUL CHAR except `\0` or `\x00` BYTE_ESCAPE except `\u{0}`, `\u{00}`, …, `\u{000000}` UNICODE_ESCAPE STRING_CONTINUE " SUFFIX

C 字符串字面量_是 Unicode 字符和_转义_的序列,前面是字符 U+0063c)和 U+0022(双引号),后面是字符 U+0022。如果字符 U+0022 存在于字面量中,它必须通过前面的 U+005C\)字符进行_转义。或者,C 字符串字面量可以是下面定义的_原始 C 字符串字面量_。

C 字符串由字节 0x00 隐式终止,因此 C 字符串字面量 c"" 等同于从字节字符串字面量 b"\x00" 手动构造 &CStr。除了隐式终止符外,字节 0x00 不允许在 C 字符串中使用。

换行符(由字符 U+000A (LF) 表示)允许在 C 字符串字面量中使用。字符 U+000D (CR) 不得出现在 C 字符串字面量中。当未转义的 U+005C 字符(\)紧接在换行符之前时,换行符不会出现在词法单元表示的字符串中。有关详细信息,请参阅字符串继续转义

在非原始 C 字符串字面量中可以使用一些额外的_转义_。转义以 U+005C\)开头,后跟以下形式之一:

  • 字节转义_以 U+0078x)开头,后跟恰好两个_十六进制数字。它表示等于所提供十六进制值的字节。
  • 24 位代码点转义_以 U+0075u)开头,后跟最多六个_十六进制数字,用大括号 U+007B{)和 U+007D})括起来。它表示等于所提供十六进制值的 Unicode 代码点,编码为 UTF-8。
  • _空白转义_是字符 U+006En)、U+0072r)或 U+0074t)之一,分别表示字节值 0x0A (ASCII LF)、0x0D (ASCII CR) 或 0x09 (ASCII HT)。
  • _反斜杠转义_是字符 U+005C\),必须进行转义才能表示其 ASCII 编码 0x5C

C 字符串表示没有定义编码的字节,但 C 字符串字面量可以包含 U+007F 以上的 Unicode 字符。此类字符将被替换为该字符 UTF-8 表示的字节。

以下 C 字符串字面量是等效的:

#![allow(unused)]
fn main() {
c"æ";        // LATIN SMALL LETTER AE (U+00E6)
c"\u{00E6}";
c"\xC3\xA6";
}

2021 Edition differences

C 字符串字面量在 2021 版本或更高版本中被接受。在早期版本中,词法单元 c"" 被词法分析为 c ""

原始 C 字符串字面量

Lexer
RAW_C_STRING_LITERAL
      cr " ^ RAW_C_STRING_CONTENT " SUFFIX?
    | cr #n:1..=255 ^ " RAW_C_STRING_CONTENT_HASHED " #n SUFFIX?

RAW_C_STRING_CONTENT → ( !" ~[CR NUL] )*

RAW_C_STRING_CONTENT_HASHED → ( !( " #n ) ~[CR NUL] )*

RAW_C_STRING_LITERAL cr " no backtracking RAW_C_STRING_CONTENT " SUFFIX cr repeat count n at most 254 more times # no backtracking " RAW_C_STRING_CONTENT_HASHED " repeat exactly n times # SUFFIX
RAW_C_STRING_CONTENT not followed by " ⚠️ with the exception of CR NUL CHAR
RAW_C_STRING_CONTENT_HASHED not followed by " repeat exactly n times # ⚠️ with the exception of CR NUL CHAR

原始 C 字符串字面量不处理任何转义。它们以字符 U+0063c)开头,后跟 U+0072r),再后跟少于 256 个字符 U+0023#)和一个 U+0022(双引号)字符。

_原始 C 字符串体_可以包含除 U+0000 (NUL) 和 U+000D (CR) 之外的任何 Unicode 字符序列。它仅由另一个 U+0022(双引号)字符终止,后跟与开头 U+0022(双引号)字符之前相同数量的 U+0023#)字符。

原始 C 字符串体中包含的所有字符都以 UTF-8 编码表示它们本身。字符 U+0022(双引号)(除非后跟至少与用于开始原始 C 字符串字面量相同数量的 U+0023#)字符)或 U+005C\)没有任何特殊含义。

2021 Edition differences

原始 C 字符串字面量在 2021 版本或更高版本中被接受。在早期版本中,词法单元 cr"" 被词法分析为 cr ""cr#""# 被词法分析为 cr #""#(这是不合语法的)。

C 字符串和原始 C 字符串字面量示例

#![allow(unused)]
fn main() {
c"foo"; cr"foo";                     // foo
c"\"foo\""; cr#""foo""#;             // "foo"

c"foo #\"# bar";
cr##"foo #"# bar"##;                 // foo #"# bar

c"\x52"; c"R"; cr"R";                // R
c"\\x52"; cr"\x52";                  // \x52
}

数字字面量

数字字面量_是_整数字面量_或_浮点数字面量。识别这两种字面量的语法是混合的。

整数字面量

Lexer
INTEGER_LITERAL
    ( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL | DEC_LITERAL )
    ^ !RESERVED_FLOAT SUFFIX?

DEC_LITERALDEC_DIGIT ( DEC_DIGIT | _ )*

BIN_LITERAL0b ^ _* BIN_DIGIT ( BIN_DIGIT | _ )* ![e E 2-9]

OCT_LITERAL0o ^ _* OCT_DIGIT ( OCT_DIGIT | _ )* ![e E 8-9]

HEX_LITERAL0x ^ _* HEX_DIGIT ( HEX_DIGIT | _ )*

BIN_DIGIT → [0-1]

OCT_DIGIT → [0-7]

DEC_DIGIT → [0-9]

HEX_DIGIT → [0-9 a-f A-F]

RESERVED_FLOAT. !( . | _ | XID_Start )

_整数字面量_有四种形式之一:

  • _十进制字面量_以十进制数字开头,后跟十进制数字和_下划线_的任意混合。
  • _十六进制字面量_以字符序列 U+0030 U+00780x)开头,后跟十六进制数字和下划线的任意混合(至少有一个数字)。
  • _八进制字面量_以字符序列 U+0030 U+006F0o)开头,后跟八进制数字和下划线的任意混合(至少有一个数字)。
  • _二进制字面量_以字符序列 U+0030 U+00620b)开头,后跟二进制数字和下划线的任意混合(至少有一个数字)。

与任何字面量一样,整数字面量后面可以(直接,没有空格)跟着上面描述的后缀。后缀不能以 eE 开头,因为那将被解释为浮点数字面量的指数。有关这些后缀的效果,请参阅整数字面量表达式

被接受为字面量表达式的整数字面量示例:

#![allow(unused)]
fn main() {
#![allow(overflowing_literals)]
123;
123i32;
123u32;
123_u32;

0xff;
0xff_u8;
0x01_f32; // integer 7986, not floating-point 1.0
0x01_e3;  // integer 483, not floating-point 1000.0

0o70;
0o70_i16;

0b1111_1111_1001_0000;
0b1111_1111_1001_0000i64;
0b________1;

0usize;

// These are too big for their type, but are accepted as literal expressions.
128_i8;
256_u8;

// This is an integer literal, accepted as a floating-point literal expression.
5f32;
}

请注意,例如 -1i8 被分析为两个词法单元:- 后跟 1i8

不被接受为字面量表达式的整数字面量示例:

#![allow(unused)]
fn main() {
#[cfg(false)] {
0invalidSuffix;
123AFB43;
0b010a;
0xAB_CD_EF_GH;
0b1111_f32;
}
}
无效整数字面量

某些整数字面量形式是无效的。为避免歧义,词法分析器会拒绝它们,而不是将它们拆分为单独的词法单元。

#![allow(unused)]
fn main() {
0b0102;  // This is not `0b010` followed by `2`.
0o1279;  // This is not `0o127` followed by `9`.
0x80.0;  // This is not `0x80` followed by `.` and `0`.
0b101e;  // This is not a suffixed literal or `0b101` followed by `e`.
0b;      // This is not an integer literal or `0` followed by `b`.
0b_;     // This is not an integer literal or `0` followed by `b_`.
2em;     // This is not a suffixed literal or `2` followed by `em`.
2.0em;   // This is not a suffixed literal or `2.0` followed by `em`.
}

如果无后缀二进制或八进制字面量后面没有中间空白地跟着超出其基数范围的十进制数字,则是错误。

如果无后缀二进制、八进制或十六进制字面量后面没有中间空白地跟着句点字符(受与浮点数字面量中相同的关于句点后可跟内容的限制),则是错误。

如果无后缀二进制或八进制字面量后面没有中间空白地跟着字符 eE,则是错误。

如果基数前缀后面(在任何可选的前导下划线之后)没有至少一个其基数的有效数字,则是错误。

元组索引

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

元组索引直接与字面量词法单元进行比较。元组索引以 0 开头,每个后续索引将值递增 1 作为十进制值。因此,只有十进制值会匹配,并且该值不能有任何额外的 0 前缀字符。

元组索引不能包含任何后缀(如 usize)。

#![allow(unused)]
fn main() {
let example = ("dog", "cat", "horse");
let dog = example.0;
let cat = example.1;
// The following examples are invalid.
let cat = example.01;  // ERROR no field named `01`
let horse = example.0b10;  // ERROR no field named `0b10`
let unicorn = example.0usize; // ERROR suffixes on a tuple index are invalid
let underscore = example.0_0; // ERROR no field `0_0` on type `(&str, &str, &str)`
}

浮点数字面量

Lexer
FLOAT_LITERAL
      DEC_LITERAL ( . DEC_LITERAL )? FLOAT_EXPONENT SUFFIX?
    | DEC_LITERAL . DEC_LITERAL SUFFIX?
    | DEC_LITERAL . !( . | _ | XID_Start )

FLOAT_EXPONENT
    ( e | E ) ^ ( + | - )? _* DEC_DIGIT ( DEC_DIGIT | _ )*

_浮点数字面量_有两种形式之一:

  • 十进制字面量_后跟句点字符 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;
}
}

如果浮点数字面量的指数没有数字,则是错误。

#![allow(unused)]
fn main() {
2e;   // This is not a floating-point literal or `2` followed by `e`.
2.0e; // This is not a floating-point literal or `2.0` followed by `e`.
}

生命周期和循环标签

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 )

生命周期参数和循环标签使用 LIFETIME_OR_LABEL 词法单元。词法分析器将接受任何 LIFETIME_TOKEN,例如可以在宏中使用。

原始生命周期类似于普通生命周期,但其标识符以 r# 为前缀。(请注意,r# 前缀不作为实际生命周期的一部分。)

与普通生命周期不同,原始生命周期可以是任何严格或保留关键字,除了上面为 RAW_LIFETIME 列出的那些。

使用 RESERVED_RAW_LIFETIME 词法单元是错误的。

2021 Edition differences

原始生命周期在 2021 版本或更高版本中被接受。在早期版本中,词法单元 'r#lt 被词法分析为 'r # lt

标点符号

标点符号词法单元用作运算符、分隔符和语法的其他部分。

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

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

Note

有关标点符号字符的使用方式,请参阅语法索引

分隔符

括号标点符号用于语法的各个部分。左括号必须始终与右括号配对。括号和其中的词法单元在中被称为“词法单元树“。三种类型的括号是:

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

保留词法单元

几种词法单元形式被保留供将来使用或为了避免混淆。源输入匹配这些形式之一是错误的。

保留前缀

Lexer
RESERVED_TOKEN_DOUBLE_QUOTE
    IDENTIFIER_OR_KEYWORDexcept b or c or r or br or cr "

RESERVED_TOKEN_SINGLE_QUOTE
    IDENTIFIER_OR_KEYWORDexcept b '

RESERVED_TOKEN_POUND
    IDENTIFIER_OR_KEYWORDexcept r or br or cr #

RESERVED_TOKEN_LIFETIME
    ' IDENTIFIER_OR_KEYWORDexcept r #

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

如果源输入在词法上被解释为非原始标识符(或关键字)并且紧接着 #'" 字符(没有中间空白),则被识别为保留前缀。

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

类似地,原始字符串字面量、字节字面量、字节字符串字面量、原始字节字符串字面量、C 字符串字面量和原始 C 字符串字面量中使用的 rbbrccr 前缀不被解释为保留前缀。

如果源输入在词法上被解释为非原始生命周期(或关键字)并且紧接着 # 字符(没有中间空白),则被识别为保留生命周期前缀。

2021 Edition differences

从 2021 版本开始,保留前缀被词法分析器报告为错误(特别是,它们不能传递给宏)。

在 2021 版本之前,保留前缀被词法分析器接受并解释为多个词法单元(例如,一个词法单元用于标识符或关键字,后跟一个 # 词法单元)。

在所有版本中都被接受的示例:

#![allow(unused)]
fn main() {
macro_rules! lexes {($($_:tt)*) => {}}
lexes!{a #foo}
lexes!{continue 'foo}
lexes!{match "..." {}}
lexes!{r#let#foo}         // three tokens: r#let # foo
lexes!{'prefix #lt}
}

在 2021 版本之前被接受但之后被拒绝的示例:

#![allow(unused)]
fn main() {
macro_rules! lexes {($($_:tt)*) => {}}
lexes!{a#foo}
lexes!{continue'foo}
lexes!{match"..." {}}
lexes!{'prefix#lt}
}

保留守卫

Lexer
RESERVED_GUARDED_STRING_LITERAL#+ STRING_LITERAL

RESERVED_POUNDS#2..

保留守卫是为将来使用保留的语法,如果使用将生成编译错误。

保留守卫字符串字面量是一个或多个 U+0023#)后紧跟 STRING_LITERAL 的词法单元。

保留井号是两个或多个 U+0023#)的词法单元。

2024 Edition differences

在 2024 版本之前,保留守卫被词法分析器接受并解释为多个词法单元。例如,#"foo"# 形式被解释为三个词法单元。## 被解释为两个词法单元。


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

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

  3. 参见 lex.token.literal.char-escape.unicode

Rust 的功能和语法可以通过称为宏(Macro)的自定义定义来扩展。它们被赋予名称,并通过一致的语法调用:some_extension!(...)

有两种定义新宏的方式:

  • 通过示例定义的宏 以更高级别的声明方式定义新语法。
  • 过程宏 使用操作输入词法单元的函数来定义函数式宏、自定义派生和自定义属性。

宏调用

Syntax
MacroInvocation
    SimplePath ! DelimTokenTree

DelimTokenTree
      ( TokenTree* )
    | [ TokenTree* ]
    | { TokenTree* }

TokenTree
    Tokenexcept delimiters | DelimTokenTree

MacroInvocationSemi
      SimplePath ! ( TokenTree* ) ;
    | SimplePath ! [ TokenTree* ] ;
    | SimplePath ! { TokenTree* }

宏调用在编译时展开宏,并用宏的结果替换调用。宏可以在以下情况下调用:

当用作项或语句时,使用 MacroInvocationSemi 形式,当不使用花括号时,末尾需要分号。可见性限定符 永远不允许在宏调用或 macro_rules 定义之前。

#![allow(unused)]
fn main() {
// Used as an expression.
let x = vec![1,2,3];

// Used as a statement.
println!("Hello!");

// Used in a pattern.
macro_rules! pat {
    ($i:ident) => (Some($i))
}

if let pat!(x) = Some(1) {
    assert_eq!(x, 1);
}

// Used in a type.
macro_rules! Tuple {
    { $A:ty, $B:ty } => { ($A, $B) };
}

type N2 = Tuple!(i32, i32);

// Used as an item.
use std::cell::RefCell;
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

// Used as an associated item.
macro_rules! const_maker {
    ($t:ty, $v:tt) => { const CONST: $t = $v; };
}
trait T {
    const_maker!{i32, 7}
}

// Macro calls within macros.
macro_rules! example {
    () => { println!("Macro call in a macro!") };
}
// Outer macro `example` is expanded, then inner macro `println` is expanded.
example!();
}

宏调用可以通过两种作用域进行解析:

通过示例定义的宏(Macros by Example)

Syntax
MacroRulesDefinition
    macro_rules ! IDENTIFIER MacroRulesDef

MacroRulesDef
      ( MacroRules ) ;
    | [ MacroRules ] ;
    | { MacroRules }

MacroRules
    MacroRule ( ; MacroRule )* ;?

MacroRule
    MacroMatcher => MacroTranscriber

MacroMatcher
      ( MacroMatch* )
    | [ MacroMatch* ]
    | { MacroMatch* }

MacroMatch
      Tokenexcept $ and delimiters
    | MacroMatcher
    | $ ( IDENTIFIER_OR_KEYWORDexcept crate | RAW_IDENTIFIER ) : MacroFragSpec
    | $ ( MacroMatch+ ) MacroRepSep? MacroRepOp

MacroFragSpec
      block | expr | expr_2021 | ident | item | lifetime | literal
    | meta | pat | pat_param | path | stmt | tt | ty | vis

MacroRepSepTokenexcept delimiters and MacroRepOp

MacroRepOp* | + | ?

MacroTranscriberDelimTokenTree

macro_rules 允许用户以声明方式定义语法扩展。我们将此类扩展称为“通过示例定义的宏“或简称为“宏“。

每个通过示例定义的宏都有一个名称和一个或多个_规则_。每个规则有两部分:匹配器(描述它匹配的语法)和_转录器_(描述将替换成功匹配的调用的语法)。匹配器和转录器都必须用分隔符括起来。宏可以展开为表达式、语句、项(包括 trait、impl 和外部项)、类型或模式。

转录

当宏被调用时,宏展开器按名称查找宏调用,并依次尝试每个宏规则。它转录第一个成功的匹配;如果这导致错误,则不再尝试未来的匹配。

匹配时不执行前瞻;如果编译器无法明确地一次确定如何解析宏调用一个词法单元,则是错误。在以下示例中,编译器不会在标识符之后向前查看以查看后面的词法单元是否是 ),即使这将允许它明确地解析调用:

#![allow(unused)]
fn main() {
macro_rules! ambiguity {
    ($($i:ident)* $j:ident) => { };
}

ambiguity!(error); // Error: local ambiguity
}

在匹配器和转录器中,$ 词法单元用于调用宏引擎的特殊行为(下面在元变量重复中描述)。不属于此类调用的词法单元被字面匹配和转录,有一个例外。例外是匹配器的外部分隔符将匹配任何一对分隔符。因此,例如,匹配器 (()) 将匹配 {()} 但不匹配 {{}}。字符 $ 不能被字面匹配或转录。

转发匹配的片段

当将匹配的片段转发到另一个通过示例定义的宏时,第二个宏中的匹配器将看到片段类型的不透明 AST。第二个宏不能使用字面词法单元来匹配匹配器中的片段,只能使用相同类型的片段说明符。identlifetimett 片段类型是例外,可以被字面词法单元匹配。以下说明了此限制:

#![allow(unused)]
fn main() {
macro_rules! foo {
    ($l:expr) => { bar!($l); }
// ERROR:               ^^ no rules expected this token in macro call
}

macro_rules! bar {
    (3) => {}
}

foo!(3);
}

以下说明了在匹配 tt 片段后如何直接匹配词法单元:

#![allow(unused)]
fn main() {
// compiles OK
macro_rules! foo {
    ($l:tt) => { bar!($l); }
}

macro_rules! bar {
    (3) => {}
}

foo!(3);
}

元变量

在匹配器中,$ name : fragment-specifier 匹配指定类型的 Rust 语法片段,并将其绑定到元变量 $name

有效的片段说明符是:

在转录器中,元变量通过 $name 简单引用,因为片段类型在匹配器中指定。元变量被匹配它们的语法元素替换。元变量可以被转录多次或根本不转录。

关键字元变量 $crate 可用于引用当前 crate。

2021 Edition differences

从 2021 版本开始,pat 片段说明符匹配顶级或模式(即它们接受 Pattern)。

在 2021 版本之前,它们匹配与 pat_param 完全相同的片段(即它们接受 PatternNoTopAlt)。

相关版本是 macro_rules! 定义生效的版本。

2024 Edition differences

在 2024 版本之前,expr 片段说明符在顶级不匹配 UnderscoreExpressionConstBlockExpression。它们在子表达式中是允许的。

expr_2021 片段说明符的存在是为了保持与 2024 版本之前的向后兼容性。

重复

在匹配器和转录器中,通过将要重复的词法单元放在 $() 内,后跟重复运算符来指示重复,可选地在中间使用分隔词法单元。

分隔词法单元可以是除分隔符或重复运算符之外的任何词法单元,但 ;, 是最常见的。例如,$( $i:ident ),* 表示任意数量的用逗号分隔的标识符。允许嵌套重复。

重复运算符是:

  • * — 表示任意数量的重复。
  • + — 表示任意数量但至少一个。
  • ? — 表示可选片段,出现零次或一次。

由于 ? 表示最多一次出现,因此不能与分隔符一起使用。

重复的片段匹配并转录为指定数量的片段,由分隔词法单元分隔。元变量匹配到其对应片段的每次重复。例如,上面的 $( $i:ident ),* 示例将 $i 匹配到列表中的所有标识符。

在转录期间,对重复应用额外的限制,以便编译器知道如何正确展开它们:

  1. 元变量在转录器中必须出现与匹配器中完全相同数量、种类和嵌套顺序的重复。因此,对于匹配器 $( $i:ident ),*,转录器 => { $i }=> { $( $( $i )* )* }=> { $( $i )+ } 都是非法的,但 => { $( $i );* } 是正确的,并将逗号分隔的标识符列表替换为分号分隔的列表。
  2. 转录器中的每个重复必须包含至少一个元变量,以决定展开多少次。如果多个元变量出现在同一重复中,它们必须绑定到相同数量的片段。例如,( $( $i:ident ),* ; $( $j:ident ),* ) => (( $( ($i,$j) ),* )) 必须绑定与 $j 片段相同数量的 $i 片段。这意味着用 (a, b, c; d, e, f) 调用宏是合法的,并展开为 ((a,d), (b,e), (c,f)),但 (a, b, c; d, e) 是非法的,因为数量不同。此要求适用于每一层嵌套重复。

作用域、导出和导入

由于历史原因,通过示例定义的宏的作用域与项的作用域不完全相同。宏有两种作用域形式:文本作用域和基于路径的作用域。文本作用域基于源文件中事物出现的顺序,甚至跨越多个文件,是默认的作用域。下面将进一步解释。基于路径的作用域的工作方式与项作用域完全相同。宏的作用域、导出和导入主要由属性控制。

当宏通过非限定标识符(不是多部分路径的一部分)调用时,首先在文本作用域中查找。如果这没有产生任何结果,则在基于路径的作用域中查找。如果宏的名称用路径限定,则仅在基于路径的作用域中查找。

use lazy_static::lazy_static; // Path-based import.

macro_rules! lazy_static { // Textual definition.
    (lazy) => {};
}

lazy_static!{lazy} // Textual lookup finds our macro first.
self::lazy_static!{} // Path-based lookup ignores our macro, finds imported one.

文本作用域

文本作用域主要基于源文件中事物出现的顺序,其工作方式类似于用 let 声明的局部变量的作用域,但它也适用于模块级别。当使用 macro_rules! 定义宏时,宏在定义之后进入作用域(请注意,它仍然可以递归使用,因为名称是从调用位置查找的),直到其周围的作用域(通常是模块)关闭。这可以进入子模块,甚至跨越多个文件:

//// src/lib.rs
mod has_macro {
    // m!{} // Error: m is not in scope.

    macro_rules! m {
        () => {};
    }
    m!{} // OK: appears after declaration of m.

    mod uses_macro;
}

// m!{} // Error: m is not in scope.

//// src/has_macro/uses_macro.rs

m!{} // OK: appears after declaration of m in src/lib.rs

多次定义宏不是错误;最近的声明将遮蔽前一个声明,除非它已超出作用域。

#![allow(unused)]
fn main() {
macro_rules! m {
    (1) => {};
}

m!(1);

mod inner {
    m!(1);

    macro_rules! m {
        (2) => {};
    }
    // m!(1); // Error: no rule matches '1'
    m!(2);

    macro_rules! m {
        (3) => {};
    }
    m!(3);
}

m!(1);
}

宏也可以在函数内部声明和本地使用,并且工作方式类似:

#![allow(unused)]
fn main() {
fn foo() {
    // m!(); // Error: m is not in scope.
    macro_rules! m {
        () => {};
    }
    m!();
}

// m!(); // Error: m is not in scope.
}

宏的文本作用域名称绑定遮蔽基于路径的作用域绑定。

#![allow(unused)]
fn main() {
macro_rules! m2 {
    () => {
        println!("m2");
    };
}

// Resolves to path-based candidate from use declaration below.
m!(); // prints "m2\n"

// Introduce second candidate for `m` with textual scope.
//
// This shadows path-based candidate from below for the rest of this
// example.
macro_rules! m {
    () => {
        println!("m");
    };
}

// Introduce `m2` macro as path-based candidate.
//
// This item is in scope for this entire example, not just below the
// use declaration.
use m2 as m;

// Resolves to the textual macro candidate from above the use
// declaration.
m!(); // prints "m\n"
}

Note

有关不允许遮蔽的区域,请参阅名称解析歧义

基于路径的作用域

默认情况下,宏没有基于路径的作用域。宏可以通过两种方式获得基于路径的作用域:

宏可以被重导出,以便从 crate 根以外的模块获得基于路径的作用域。

#![allow(unused)]
fn main() {
mac::m!(); // OK: Path-based lookup finds `m` in the mac module.

mod mac {
    // Introduce macro `m` with textual scope.
    macro_rules! m {
        () => {};
    }

    // Reexport with path-based scope from within `m`'s textual scope.
    pub(crate) use m;
}
}

宏具有隐式可见性 pub(crate)#[macro_export] 将隐式可见性更改为 pub

#![allow(unused)]
fn main() {
// Implicit visibility is `pub(crate)`.
macro_rules! private_m {
    () => {};
}

// Implicit visibility is `pub`.
#[macro_export]
macro_rules! pub_m {
    () => {};
}

pub(crate) use private_m as private_macro; // OK.
pub use pub_m as pub_macro; // OK.
}
#![allow(unused)]
fn main() {
// Implicit visibility is `pub(crate)`.
macro_rules! private_m {
    () => {};
}

// Implicit visibility is `pub`.
#[macro_export]
macro_rules! pub_m {
    () => {};
}

pub(crate) use private_m as private_macro; // OK.
pub use pub_m as pub_macro; // OK.

pub use private_m; // ERROR: `private_m` is only public within
                   // the crate and cannot be re-exported outside.
}

macro_use 属性

macro_use 属性 有两个用途:它可用于模块以扩展其中定义的宏的作用域,也可用于 extern crate 以将宏从另一个 crate 导入到 macro_use prelude 中。

Example

#![allow(unused)]
fn main() {
#[macro_use]
mod inner {
    macro_rules! m {
        () => {};
    }
}
m!();
}
#[macro_use]
extern crate log;

在模块上使用时,macro_use 属性使用 MetaWord 语法。

extern crate 上使用时,它使用 MetaWordMetaListIdents 语法。有关如何使用这些语法的更多信息,请参阅 macro.decl.scope.macro_use.prelude

macro_use 属性可应用于模块或 extern crate

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

macro_use 属性不能在 extern crate self 上使用。

macro_use 属性可以在一个形式上使用任意次数。

可以指定多个 MetaListIdents 语法的 macro_use 实例。所有指定宏的并集将被导入。

Note

在模块上,rustc 会对第一个之后的任何 MetaWord macro_use 属性发出 lint 警告。

extern crate 上,rustc 会对由于没有导入任何未被另一个 macro_use 属性导入的宏而无效的任何 macro_use 属性发出 lint 警告。如果两个或多个 MetaListIdents macro_use 属性导入相同的宏,则第一个会被 lint。如果存在任何 MetaWord macro_use 属性,则所有 MetaListIdents macro_use 属性都会被 lint。如果存在两个或多个 MetaWord macro_use 属性,则第一个之后的会被 lint。

在模块上使用 macro_use 时,模块的宏作用域扩展到模块的词法作用域之外。

Example

#![allow(unused)]
fn main() {
#[macro_use]
mod inner {
    macro_rules! m {
        () => {};
    }
}
m!(); // OK
}

在 crate 根中的 extern crate 声明上指定 macro_use 会从该 crate 导入导出的宏。

以这种方式导入的宏被导入到 macro_use prelude 中,而不是文本导入,这意味着它们可以被任何其他名称遮蔽。由 macro_use 导入的宏可以在导入语句之前使用。

Note

rustc 目前在冲突时优先选择最后导入的宏。不要依赖这一点。此行为是不寻常的,因为 Rust 中的导入通常是顺序无关的。macro_use 的此行为将来可能会更改。

有关详细信息,请参阅 Rust issue #148025

使用 MetaWord 语法时,导入所有导出的宏。使用 MetaListIdents 语法时,仅导入指定的宏。

Example

#[macro_use(lazy_static)] // Or `#[macro_use]` to import all macros.
extern crate lazy_static;

lazy_static!{}
// self::lazy_static!{} // ERROR: lazy_static is not defined in `self`.

要用 macro_use 导入的宏必须用 macro_export 导出。

macro_export 属性

macro_export 属性 从 crate 导出宏,并使其在 crate 根中可用以进行基于路径的解析。

Example

#![allow(unused)]
fn main() {
self::m!();
//  ^^^^ OK: Path-based lookup finds `m` in the current module.
m!(); // As above.

mod inner {
    super::m!();
    crate::m!();
}

mod mac {
    #[macro_export]
    macro_rules! m {
        () => {};
    }
}
}

macro_export 属性使用 MetaWordMetaListIdents 语法。使用 MetaListIdents 语法时,它接受单个 local_inner_macros 值。

macro_export 属性可应用于 macro_rules 定义。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

只有宏上的第一次使用 macro_export 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

默认情况下,宏只有[文本作用域]textual scope,无法通过路径解析。当使用 macro_export 属性时,宏在 crate 根中可用,并可以通过其路径引用。

Example

没有 macro_export,宏只有文本作用域,因此宏的基于路径的解析失败。

macro_rules! m {
    () => {};
}
self::m!(); // ERROR
crate::m!(); // ERROR
fn main() {}

使用 macro_export,基于路径的解析有效。

#[macro_export]
macro_rules! m {
    () => {};
}
self::m!(); // OK
crate::m!(); // OK
fn main() {}

macro_export 属性使宏从 crate 根导出,以便可以在其他 crate 中通过路径引用。

Example

log crate 中给定以下内容:

#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! warn {
    ($message:expr) => { eprintln!("WARN: {}", $message) };
}
}

从另一个 crate,您可以通过路径引用宏:

fn main() {
    log::warn!("example warning");
}

macro_export 允许在 extern crate 上使用 macro_use 将宏导入到 macro_use prelude 中。

Example

log crate 中给定以下内容:

#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! warn {
    ($message:expr) => { eprintln!("WARN: {}", $message) };
}
}

在依赖 crate 中使用 macro_use 允许您从 prelude 使用宏:

#[macro_use]
extern crate log;

pub mod util {
    pub fn do_thing() {
        // Resolved via macro prelude.
        warn!("example warning");
    }
}

macro_export 属性中添加 local_inner_macros 会导致宏定义中的所有单段宏调用具有隐式的 $crate:: 前缀。

Note

这主要用作将 $crate 添加到语言之前编写的代码迁移到 Rust 2018 的基于路径的宏导入的工具。不鼓励在新代码中使用。

Example

#![allow(unused)]
fn main() {
#[macro_export(local_inner_macros)]
macro_rules! helped {
    () => { helper!() } // Automatically converted to $crate::helper!().
}

#[macro_export]
macro_rules! helper {
    () => { () }
}
}

卫生性

通过示例定义的宏具有_混合站点卫生性_。这意味着循环标签块标签和局部变量在宏定义站点查找,而其他符号在宏调用站点查找。例如:

#![allow(unused)]
fn main() {
let x = 1;
fn func() {
    unreachable!("this is never called")
}

macro_rules! check {
    () => {
        assert_eq!(x, 1); // Uses `x` from the definition site.
        func();           // Uses `func` from the invocation site.
    };
}

{
    let x = 2;
    fn func() { /* does not panic */ }
    check!();
}
}

在宏展开中定义的标签和局部变量不会在调用之间共享,因此此代码无法编译:

#![allow(unused)]
fn main() {
macro_rules! m {
    (define) => {
        let x = 1;
    };
    (refer) => {
        dbg!(x);
    };
}

m!(define);
m!(refer);
}

一个特殊情况是 $crate 元变量。它指的是定义宏的 crate,可以在路径开头使用以查找调用站点中不在作用域中的项或宏。

//// Definitions in the `helper_macro` crate.
#[macro_export]
macro_rules! helped {
    // () => { helper!() } // This might lead to an error due to 'helper' not being in scope.
    () => { $crate::helper!() }
}

#[macro_export]
macro_rules! helper {
    () => { () }
}

//// Usage in another crate.
// Note that `helper_macro::helper` is not imported!
use helper_macro::helped;

fn unit() {
    helped!();
}

请注意,因为 $crate 指的是当前 crate,所以在引用非宏项时必须使用完全限定的模块路径:

#![allow(unused)]
fn main() {
pub mod inner {
    #[macro_export]
    macro_rules! call_foo {
        () => { $crate::inner::foo() };
    }

    pub fn foo() {}
}
}

此外,即使 $crate 允许宏在展开时引用其自身 crate 内的项,其使用对可见性没有影响。引用的项或宏在调用站点必须仍然可见。在以下示例中,任何从其外部调用 call_foo!() 的尝试都将失败,因为 foo() 不是公开的。

#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! call_foo {
    () => { $crate::foo() };
}

fn foo() {}
}

Note

在 Rust 1.30 之前,$cratelocal_inner_macros 不受支持。它们与基于路径的宏导入一起添加,以确保辅助宏不需要由宏导出 crate 的用户手动导入。为早期版本 Rust 编写的使用辅助宏的 crate 需要修改为使用 $cratelocal_inner_macros,以便与基于路径的导入良好配合。

跟随集歧义限制

宏系统使用的解析器相当强大,但它是有限的,以防止当前或未来版本的语言中出现歧义。

特别是,除了关于歧义展开的规则外,由元变量匹配的非终结符后面必须跟着一个已被决定可以安全地在该种匹配之后使用的词法单元。

例如,像 $i:expr [ , ] 这样的宏匹配器理论上今天可以在 Rust 中接受,因为 [[,] 不能是合法表达式的一部分,因此解析将始终是明确的。然而,因为 [ 可以开始尾随表达式,[ 不是可以在表达式之后安全排除的字符。如果 [[,] 在更高版本的 Rust 中被接受,此匹配器将变得有歧义或错误解析,破坏可工作的代码。像 $i:expr,$i:expr; 这样的匹配器将是合法的,因为 ,; 是合法的表达式分隔符。具体规则是:

  • exprstmt 后面只能跟以下之一:=>,;
  • pat_param 后面只能跟以下之一:=>,=|ifin
  • pat 后面只能跟以下之一:=>,=ifin
  • pathty 后面只能跟以下之一:=>,=|;:>>>[{aswhereblock 片段说明符的宏变量。
  • vis 后面只能跟以下之一:,、除 priv 外的标识符、可以开始类型的任何词法单元,或具有 identtypath 片段说明符的元变量。
  • 所有其他片段说明符没有限制。

2021 Edition differences

在 2021 版本之前,pat 后面也可以跟 |

当涉及重复时,规则适用于每个可能的展开数量,考虑分隔符。这意味着:

  • 如果重复包含分隔符,则该分隔符必须能够跟在重复内容之后。
  • 如果重复可以重复多次(*+),则内容必须能够跟在自己之后。
  • 重复的内容必须能够跟在前面的内容之后,后面的内容必须能够跟在重复的内容之后。
  • 如果重复可以匹配零次(*?),则后面的内容必须能够跟在前面的内容之后。

有关更多详细信息,请参阅正式规范

过程宏

过程宏允许将语法扩展创建为函数的执行。过程宏有三种形式:

过程宏允许您在编译时运行代码,这些代码操作 Rust 语法,既消费又产生 Rust 语法。您可以将过程宏视为从一个 AST 到另一个 AST 的函数。

过程宏必须在 crate 类型proc-macro 的 crate 根中定义。这些宏不能在定义它们的 crate 中使用,只能在导入到另一个 crate 时使用。

Note

使用 Cargo 时,过程宏 crate 在清单中用 proc-macro 键定义:

[lib]
proc-macro = true

作为函数,它们必须返回语法、panic 或无限循环。返回的语法根据过程宏的种类替换或添加语法。Panic 被编译器捕获并转换为编译器错误。无限循环不会被编译器捕获,这会挂起编译器。

过程宏在编译期间运行,因此拥有与编译器相同的资源。例如,标准输入、错误和输出与编译器可以访问的相同。类似地,文件访问也相同。因此,过程宏与 Cargo 的构建脚本具有相同的安全问题。

过程宏有两种报告错误的方式。第一种是 panic。第二种是发出 compile_error 宏调用。

proc_macro crate

过程宏 crate 几乎总是链接到编译器提供的 proc_macro crateproc_macro crate 提供了编写过程宏所需的类型和使其更容易的工具。

此 crate 主要包含 TokenStream 类型。过程宏操作词法单元流而不是 AST 节点,这对编译器和过程宏来说都是更稳定的接口。词法单元流大致等同于 Vec<TokenTree>,其中 TokenTree 可以大致被认为是词法单元。例如,fooIdent 词法单元,.Punct 词法单元,1.2Literal 词法单元。TokenStream 类型与 Vec<TokenTree> 不同,克隆成本低廉。

所有词法单元都有一个关联的 SpanSpan 是不能修改但可以制造的不透明值。Span 表示程序中源代码的范围,主要用于错误报告。虽然您不能修改 Span 本身,但您始终可以更改与任何词法单元关联Span,例如从另一个词法单元获取 Span

过程宏卫生性

过程宏是不卫生的。这意味着它们的行为就像输出词法单元流只是简单地内联写入其旁边的代码一样。这意味着它受外部项的影响,也影响外部导入。

宏作者需要小心,确保他们的宏在此限制下在尽可能多的上下文中工作。这通常包括使用库中项的绝对路径(例如,::std::option::Option 而不是 Option)或确保生成的函数名称不太可能与其他函数冲突(如 __internal_foo 而不是 foo)。

proc_macro 属性

proc_macro 属性 定义了函数式过程宏。

Example

此宏定义忽略其输入并在其作用域中发出函数 answer

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}

我们可以在二进制 crate 中使用它来将 “42” 打印到标准输出。

extern crate proc_macro_examples;
use proc_macro_examples::make_answer;

make_answer!();

fn main() {
    println!("{}", answer());
}

proc_macro 属性使用 MetaWord 语法。

proc_macro 属性只能应用于类型为 fn(TokenStream) -> TokenStreampub 函数,其中 TokenStream 来自 proc_macro crate。它必须具有“Rust” ABI。不允许使用其他函数限定符。它必须位于 crate 的根中。

proc_macro 属性只能在函数上指定一次。

proc_macro 属性在 crate 根的宏命名空间中以与函数相同的名称公开定义宏。

函数式过程宏的函数式宏调用将宏调用分隔符内的内容作为输入 TokenStream 参数传递,并用函数的输出 TokenStream 替换整个宏调用。

函数式过程宏可以在任何宏调用位置调用,包括:

proc_macro_derive 属性

proc_macro_derive [属性] 应用于函数会定义一个派生宏,可以通过 [derive 属性]调用。这些宏被给予结构体枚举联合体定义的词法单元流,并可以在其后发出新的。它们还可以声明和使用派生宏辅助属性

Example

此派生宏忽略其输入并附加定义函数的词法单元。

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_item: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}

要使用它,我们可以编写:

extern crate proc_macro_examples;
use proc_macro_examples::AnswerFn;

#[derive(AnswerFn)]
struct Struct;

fn main() {
    assert_eq!(42, answer());
}

proc_macro_derive 属性的语法是:

Syntax
ProcMacroDeriveAttribute
    proc_macro_derive ( DeriveMacroName ( , DeriveMacroAttributes )? ,? )

DeriveMacroNameIDENTIFIER

DeriveMacroAttributes
    attributes ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )

派生宏的名称由 DeriveMacroName 给出。可选的 attributes 参数在 macro.proc.derive.attributes 中描述。

proc_macro_derive 属性只能应用于在 crate 根中定义的具有 Rust ABIpub 函数,类型为 fn(TokenStream) -> TokenStream,其中 TokenStream 来自 proc_macro crate。函数可以是 const,可以使用 extern 显式指定 Rust ABI,但不能使用任何其他限定符(例如,不能是 asyncunsafe)。

proc_macro_derive 属性只能在函数上使用一次。

proc_macro_derive 属性在 crate 根的宏命名空间中公开定义派生宏。

输入 TokenStream 是应用 derive 属性的项的词法单元流。输出 TokenStream 必须是(可能为空的)项集合。这些项在输入项之后附加到同一模块中。

派生宏辅助属性

派生宏可以声明派生宏辅助属性,以在派生宏应用的的作用域内使用。这些属性惰性的。虽然它们的目的是由声明它们的宏使用,但任何宏都可以看到它们。

派生宏的辅助属性通过将其标识符添加到 proc_macro_derive 属性中的 attributes 列表来声明。

Example

此示例声明辅助属性然后忽略它。

#![crate_type="proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_derive(WithHelperAttr, attributes(helper))]
pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream {
    TokenStream::new()
}

要使用它,我们可以编写:

#[derive(WithHelperAttr)]
struct Struct {
    #[helper] field: (),
}

当派生宏调用应用于项时,该派生宏引入的辅助属性在以下情况下进入作用域:1)应用于该项且在派生宏调用之后词法出现的属性,以及 2)应用于项内字段和变体的属性。

Note

rustc 目前允许在引入它们的宏之前使用派生辅助。这种无序使用的派生辅助不能遮蔽其他属性宏。此行为已弃用并计划移除。

#[helper] // Deprecated, hard error in the future.
#[derive(WithHelperAttr)]
struct Struct {
    field: (),
}

有关详细信息,请参阅 Rust issue #79202

proc_macro_attribute 属性

proc_macro_attribute 属性 定义了一个属性宏,可用作[外部属性][outer attributes]。

Example

此属性宏接受输入流并按原样发出它,实际上是无操作属性。

#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn return_as_is(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

Example

此示例在编译器输出中显示属性宏看到的字符串化 TokenStream

// my-macro/src/lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {
    println!("attr: \"{attr}\"");
    println!("item: \"{item}\"");
    item
}
// src/lib.rs
extern crate my_macro;

use my_macro::show_streams;

// Example: Basic function.
#[show_streams]
fn invoke1() {}
// out: attr: ""
// out: item: "fn invoke1() {}"

// Example: Attribute with input.
#[show_streams(bar)]
fn invoke2() {}
// out: attr: "bar"
// out: item: "fn invoke2() {}"

// Example: Multiple tokens in the input.
#[show_streams(multiple => tokens)]
fn invoke3() {}
// out: attr: "multiple => tokens"
// out: item: "fn invoke3() {}"

// Example: Delimiters in the input.
#[show_streams { delimiters }]
fn invoke4() {}
// out: attr: "delimiters"
// out: item: "fn invoke4() {}"

proc_macro_attribute 属性使用 MetaWord 语法。

proc_macro_attribute 属性只能应用于类型为 fn(TokenStream, TokenStream) -> TokenStreampub 函数,其中 TokenStream 来自 proc_macro crate。它必须具有“Rust” ABI。不允许使用其他函数限定符。它必须位于 crate 的根中。

proc_macro_attribute 属性只能在函数上指定一次。

proc_macro_attribute 属性在 crate 根的宏命名空间中以与函数相同的名称定义属性。

属性宏只能用于:

第一个 TokenStream 参数是属性名称之后但不包括外部分隔符的分隔词法单元树。如果应用的属性仅包含属性名称或属性名称后跟空分隔符,则 TokenStream 为空。

第二个 TokenStream的其余部分,包括上的其他属性

应用属性的项被返回的 TokenStream 中的零个或多个项替换。

声明宏词法单元和过程宏词法单元

声明宏 macro_rules 和过程宏使用相似但不同的词法单元定义(或者更准确地说是 [TokenTree])。

macro_rules 中的词法单元树(对应于 tt 匹配器)定义为

  • 分隔组((...){...} 等)
  • 语言支持的所有运算符,包括单字符和多字符(++=)。
    • 请注意,此集合不包括单引号 '
  • 字面量("string"1 等)
    • 请注意,取反(例如 -1)从来不是此类字面量词法单元的一部分,而是单独的运算符词法单元。
  • 标识符,包括关键字(identr#identfn
  • 生命周期('ident
  • macro_rules 中的元变量替换(例如,在 mac 展开后 macro_rules! mac { ($my_expr: expr) => { $my_expr } } 中的 $my_expr,无论传递的表达式如何,都将被视为单个词法单元树)

过程宏中的词法单元树定义为

  • 分隔组((...){...} 等)
  • 语言支持的运算符中使用的所有标点字符(+,但不是 +=),以及单引号 ' 字符(通常用于生命周期,有关生命周期拆分和连接行为,请参见下文)
  • 字面量("string"1 等)
    • 支持取反(例如 -1)作为整数和浮点字面量的一部分。
  • 标识符,包括关键字(identr#identfn

当词法单元流传递到过程宏和从过程宏传递时,会考虑这两个定义之间的不匹配。请注意,下面的转换可能延迟发生,因此如果词法单元实际上未被检查,它们可能不会发生。

传递到 proc-macro 时

  • 所有多字符运算符都被拆分为单个字符。
  • 生命周期被拆分为 ' 字符和标识符。
  • 关键字元变量 $crate 作为单个标识符传递。
  • 所有其他元变量替换都表示为其底层词法单元流。
    • 当需要保留解析优先级时,此类词法单元流可能被包装到具有隐式分隔符(Delimiter::None)的分隔组(Group)中。
    • ttident 替换永远不会被包装到此类组中,始终表示为其底层词法单元树。

从 proc 宏发出时

  • 当适用时,标点字符被粘合成多字符运算符。
  • 与标识符连接的单引号 ' 被粘合成生命周期。
  • 负字面量被转换为两个词法单元(- 和字面量),当需要保留解析优先级时,可能被包装到具有隐式分隔符(Delimiter::None)的分隔组(Group)中。

请注意,声明宏和过程宏都不支持文档注释词法单元(例如 /// Doc),因此在传递给宏时,它们总是被转换为表示其等效 #[doc = r"str"] 属性的词法单元流。

包和源文件

Syntax
Crate
    InnerAttribute*
    Item*

Note

虽然 Rust 与任何其他语言一样,可以通过解释器和编译器来实现,但现有的唯一实现是编译器,并且该语言一直是为编译而设计的。因此,本节假定使用编译器。

Rust 的语义遵循编译时和运行时之间的阶段区分1 具有静态解释的语义规则支配编译的成功或失败,而具有动态解释的语义规则支配程序在运行时的行为。

编译模型以称为 crate 的工件为中心。每个编译处理一个源形式的 crate,如果成功,则产生一个二进制形式的 crate:可执行文件或某种库。2

crate 是编译和链接的单元,也是版本控制、分发和运行时加载的单元。crate 包含嵌套模块作用域的_树_。此树的顶层是一个模块,它是匿名的(从模块内的路径角度来看),crate 内的任何项都有一个规范的模块路径,表示其在 crate 模块树中的位置。

Rust 编译器始终以单个源文件作为输入调用,并始终产生单个输出 crate。该源文件的处理可能导致其他源文件作为模块加载。源文件的扩展名为 .rs

Rust 源文件描述一个模块,该模块的名称和位置 — 在当前 crate 的模块树中 — 是从源文件外部定义的:要么通过引用源文件中的显式 Module 项,要么通过 crate 本身的名称。

每个源文件都是一个模块,但不是每个模块都需要自己的源文件:模块定义可以嵌套在一个文件中。

每个源文件包含零个或多个 Item 定义的序列,并且可以选择以应用于包含模块的任意数量的属性开头,其中大部分会影响编译器的行为。

匿名 crate 模块可以有应用于整个 crate 的额外属性。

Note

文件的内容前面可以有 shebang

#![allow(unused)]
fn main() {
// Specify the crate name.
#![crate_name = "projx"]

// Specify the type of output artifact.
#![crate_type = "lib"]

// Turn on a warning.
// This can be done in any module, not just the anonymous crate module.
#![warn(non_camel_case_types)]
}

Main 函数

包含 main 函数的 crate 可以编译为可执行文件。

如果存在 main 函数,它必须不带参数,不能声明任何 trait 或生命周期约束,不能有任何 where 子句,并且其返回类型必须实现 Termination trait。

fn main() {}
fn main() -> ! {
    std::process::exit(0);
}
fn main() -> impl std::process::Termination {
    std::process::ExitCode::SUCCESS
}

main 函数可以是导入的,例如来自外部 crate 或当前 crate。

#![allow(unused)]
fn main() {
mod foo {
    pub fn bar() {
        println!("Hello, world!");
    }
}
use foo::bar as main;
}

Note

标准库中具有 Termination 实现的类型包括:

未捕获的外部展开

当“外部“展开(例如,从 C++ 代码抛出的异常,或使用不同 panic 处理器的 Rust 代码中的 panic!)传播到 main 函数之外时,进程将被安全终止。这可能以中止的形式发生,在这种情况下,不保证执行任何 Drop 调用,并且错误输出可能不如运行时被“本机“ Rust panic 终止时那么信息丰富。

有关更多信息,请参阅 panic 文档

no_main 属性

no_main 属性 可以应用于 crate 级别,以禁用为可执行二进制文件发出 main 符号。当链接的其他对象定义了 main 时,这很有用。

crate_name 属性

crate_name 属性 可以应用于 crate 级别,以使用 MetaNameValueStr 语法指定 crate 的名称。

#![allow(unused)]
#![crate_name = "mycrate"]
fn main() {
}

crate 名称不能为空,并且只能包含 Unicode 字母数字_ (U+005F) 字符。


  1. 这种区分在解释器中也会存在。静态检查(如语法分析、类型检查和 lint)应该在程序执行之前进行,无论何时执行。

  2. crate 在某种程度上类似于 ECMA-335 CLI 模型中的程序集,SML/NJ 编译管理器中的,Owens 和 Flatt 模块系统中的单元,或 Mesa 中的配置

条件编译

条件编译的源代码是仅在某些条件下编译的源代码。

源代码可以使用 cfgcfg_attr 以及内置的 cfg!cfg_select! 宏进行条件编译。

是否编译可以取决于编译的 crate 的目标架构、传递给编译器的任意值以及下面进一步描述的其他内容。

每种形式的条件编译都接受一个_配置谓词_,该谓词求值为 true 或 false。谓词是以下之一:

  • 配置选项。如果选项已设置,则谓词为 true;如果未设置,则为 false。
  • all() 带有逗号分隔的配置谓词列表。如果所有给定谓词都为 true,或列表为空,则为 true。
  • any() 带有逗号分隔的配置谓词列表。如果至少一个给定谓词为 true,则为 true。如果没有谓词,则为 false。
  • not() 带有配置谓词。如果其谓词为 false,则为 true;如果其谓词为 true,则为 false。
  • truefalse 字面量,分别始终为 true 或 false。

_配置选项_是名称或键值对,可以是已设置或未设置。

名称写为单个标识符,例如 unix

键值对写为标识符、=,然后是字符串,例如 target_arch = "x86_64"

Note

= 周围的空白被忽略,因此 foo="bar"foo = "bar" 是等效的。

键不需要是唯一的。例如,feature = "std"feature = "serde" 可以同时设置。

已设置的配置选项

哪些配置选项已设置是在 crate 编译期间静态确定的。

某些选项是基于编译数据的_编译器设置_。

其他选项是基于传递给编译器的代码外部输入的_任意设置_。

无法从正在编译的 crate 的源代码内部设置配置选项。

Note

对于 rustc,任意设置的配置选项使用 --cfg 标志设置。指定目标的配置值可以通过 rustc --print cfg --target $TARGET 显示。

Note

键为 feature 的配置选项是 Cargo 用于指定编译时选项和可选依赖项的约定。

target_arch

键值选项,使用目标的 CPU 架构设置一次。该值类似于平台目标三元组的第一个元素,但不完全相同。

示例值:

  • "x86"
  • "x86_64"
  • "mips"
  • "powerpc"
  • "powerpc64"
  • "arm"
  • "aarch64"

target_feature

键值选项,为当前编译目标可用的每个平台功能设置。

示例值:

  • "avx"
  • "avx2"
  • "crt-static"
  • "rdrand"
  • "sse"
  • "sse2"
  • "sse4.1"

有关可用功能的更多详细信息,请参阅 [target_feature 属性]。

target_feature 选项有一个额外的功能 crt-static,用于指示静态 C 运行时可用。

target_os

键值选项,使用目标的操作系统设置一次。该值类似于平台目标三元组的第二和第三个元素。

示例值:

  • "windows"
  • "macos"
  • "ios"
  • "linux"
  • "android"
  • "freebsd"
  • "dragonfly"
  • "openbsd"
  • "netbsd"
  • "none"(通常用于嵌入式目标)

target_family

键值选项,提供更通用的目标描述,例如目标通常所属的操作系统系列或架构系列。可以设置任意数量的 target_family 键值对。

示例值:

  • "unix"
  • "windows"
  • "wasm"
  • 同时为 "unix""wasm"

unixwindows

如果设置了 target_family = "unix",则设置 unix

如果设置了 target_family = "windows",则设置 windows

target_env

键值选项,设置有关目标平台的进一步消歧信息,包括 ABI 或使用的 libc 信息。由于历史原因,此值仅在实际需要消歧时定义为非空字符串。因此,例如,在许多 GNU 平台上,此值将为空。该值类似于平台目标三元组的第四个元素。一个区别是嵌入式 ABI(如 gnueabihf)将简单地将 target_env 定义为 "gnu"

示例值:

  • ""
  • "gnu"
  • "msvc"
  • "musl"
  • "sgx"
  • "sim"
  • "macabi"

target_abi

键值选项,设置有关目标 ABI 的信息以进一步消歧目标。

由于历史原因,此值仅在实际需要消歧时定义为非空字符串。因此,例如,在许多 GNU 平台上,此值将为空。

示例值:

  • ""
  • "llvm"
  • "eabihf"
  • "abi64"

target_endian

键值选项,根据目标 CPU 的字节序设置一次,值为 “little” 或 “big”。

target_pointer_width

键值选项,使用目标的指针宽度(以位为单位)设置一次。

示例值:

  • "16"
  • "32"
  • "64"

target_vendor

键值选项,使用目标的供应商设置一次。

示例值:

  • "apple"
  • "fortanix"
  • "pc"
  • "unknown"

target_has_atomic

键值选项,为目标支持原子加载、存储和比较交换操作的每个位宽设置。

当此 cfg 存在时,所有稳定的 core::sync::atomic API 都可用于相关的原子宽度。

可能的值:

  • "8"
  • "16"
  • "32"
  • "64"
  • "128"
  • "ptr"

target_has_atomic_primitive_alignment

键值选项,为[原子]atomic类型与相应整数类型具有相同对齐的每个位宽设置。

Note

对于给定的位宽,对齐通常相同。然而,在某些目标(如 32 位 x86)上,64 位原子类型(如 AtomicI64)的对齐为 8 字节,而 i64 仅对齐到 4 字节。在这种情况下,不设置 target_has_atomic_primitive_alignment = "64"

可能的值:

  • "8"
  • "16"
  • "32"
  • "64"
  • "128"
  • "ptr"

test

编译测试工具时启用。使用 rustc 时通过 --test 标志完成。有关测试支持的更多信息,请参阅测试

debug_assertions

在没有优化编译时默认启用。这可用于在开发中启用额外的调试代码,但在生产中不启用。例如,它控制标准库的 debug_assert! 宏的行为。

proc_macro

当正在编译的 crate 使用 proc_macrocrate 类型编译时设置。

panic

键值选项,根据 panic 策略设置。请注意,将来可能会添加更多值。

示例值:

  • "abort"
  • "unwind"

条件编译的形式

cfg 属性

*cfg [属性]*根据配置谓词有条件地包含附加到的形式。

Example

#![allow(unused)]
fn main() {
// The function is only included in the build when compiling for macOS
#[cfg(target_os = "macos")]
fn macos_only() {
  // ...
}

// This function is only included when either foo or bar is defined
#[cfg(any(foo, bar))]
fn needs_foo_or_bar() {
  // ...
}

// This function is only included when compiling for a unixish OS with a 32-bit
// architecture
#[cfg(all(unix, target_pointer_width = "32"))]
fn on_32bit_unix() {
  // ...
}

// This function is only included when foo is not defined
#[cfg(not(foo))]
fn needs_not_foo() {
  // ...
}

// This function is only included when the panic strategy is set to unwind
#[cfg(panic = "unwind")]
fn when_unwinding() {
  // ...
}
}

cfg 属性的语法是:

Syntax
CfgAttributecfg ( ConfigurationPredicate )

cfg 属性可以在允许属性的任何地方使用。

cfg 属性可以在一个形式上使用任意次数。如果任何 cfg 谓词为 false,则附加属性的形式将不被包含,但 cfg.attr.crate-level-attrs 中描述的除外。

如果谓词为 true,则该形式被重写为不带 cfg 属性。如果任何谓词为 false,则该形式从源代码中移除。

当 crate 级别的 cfg 具有 false 谓词时,crate 本身仍然存在。cfg 之前的任何 crate 属性被保留,cfg 之后的任何 crate 属性以及所有以下 crate 内容被移除。

Example

不移除前面属性的行为允许您执行诸如包含 #![no_std] 以避免链接 std 的操作,即使 #![cfg(...)] 已经以其他方式移除了 crate 的内容。例如:

// This `no_std` attribute is kept even though the crate-level `cfg`
// attribute is false.
#![no_std]
#![cfg(false)]

// This function is not included.
pub fn example() {}

cfg_attr 属性

*cfg_attr [属性]*根据配置谓词有条件地包含属性。

Example

以下模块将根据目标在 linux.rswindows.rs 中找到。

#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod os;

cfg_attr 属性的语法是:

Syntax
CfgAttrAttributecfg_attr ( ConfigurationPredicate , CfgAttrs? )

CfgAttrsAttr ( , Attr )* ,?

cfg_attr 属性可以在允许属性的任何地方使用。

cfg_attr 属性可以在一个形式上使用任意次数。

crate_typecrate_name 属性不能与 cfg_attr 一起使用。

当配置谓词为 true 时,cfg_attr 展开为谓词之后列出的属性。

可以列出零个、一个或多个属性。多个属性将各自展开为单独的属性。

Example

#[cfg_attr(feature = "magic", sparkles, crackles)]
fn bewitched() {}

// When the `magic` feature flag is enabled, the above will expand to:
#[sparkles]
#[crackles]
fn bewitched() {}

Note

cfg_attr 可以展开为另一个 cfg_attr。例如,#[cfg_attr(target_os = "linux", cfg_attr(feature = "multithreaded", some_other_attribute))] 是有效的。此示例等同于 #[cfg_attr(all(target_os = "linux", feature = "multithreaded"), some_other_attribute)]

cfg

内置的 cfg 宏接受单个配置谓词,当谓词为 true 时求值为 true 字面量,当为 false 时求值为 false 字面量。

例如:

#![allow(unused)]
fn main() {
let machine_kind = if cfg!(unix) {
  "unix"
} else if cfg!(windows) {
  "windows"
} else {
  "unknown"
};

println!("I'm running on a {} machine!", machine_kind);
}

cfg_select

内置的 cfg_select! 宏可用于在编译时根据多个配置谓词选择代码。

Example

#![allow(unused)]
fn main() {
cfg_select! {
    unix => {
        fn foo() { /* unix specific functionality */ }
    }
    target_pointer_width = "32" => {
        fn foo() { /* non-unix, 32-bit functionality */ }
    }
    _ => {
        fn foo() { /* fallback implementation */ }
    }
}

let is_unix_str = cfg_select! {
    unix => "unix",
    _ => "not unix",
};
}

Syntax
CfgSelectCfgSelectArms?

CfgSelectArms
    CfgSelectConfigurationPredicate =>
    (
        { ^ TokenTree } ,? CfgSelectArms?
      | ExpressionWithBlockNoAttrs ,? CfgSelectArms?
      | ExpressionWithoutBlockNoAttrs ( , CfgSelectArms? )?
    )

CfgSelectConfigurationPredicate
    ConfigurationPredicate | _

cfg_select 展开为第一个配置谓词求值为 true 的分支的有效负载。

如果整个有效负载用花括号括起来,则在展开期间移除花括号。

配置谓词 _ 始终求值为 true。

如果没有谓词求值为 true,则是编译错误。

每个右侧必须是宏调用位置的语法有效展开。

Syntax
Item
    OuterAttribute* ( VisItem | MacroItem )

VisItem
    Visibility?
    (
        Module
      | ExternCrate
      | UseDeclaration
      | Function
      | TypeAlias
      | Struct
      | Enumeration
      | Union
      | ConstantItem
      | StaticItem
      | Trait
      | Implementation
      | ExternBlock
    )

MacroItem
      MacroInvocationSemi
    | MacroRulesDefinition

(Item)是 crate 的组成部分。项通过嵌套的模块集合在 crate 内组织。每个 crate 都有一个“最外层“的匿名模块;crate 内的所有其他项在 crate 的模块树中都有路径

项在编译时完全确定,通常在执行期间保持固定,并且可能驻留在只读内存中。

有几种类型的项:

项可以在crate 的根模块块表达式中声明。

项的一个子集,称为关联项,可以在 trait实现中声明。

项的一个子集,称为外部项,可以在 extern中声明。

项可以按任何顺序定义,但 macro_rules 除外,它有自己的作用域行为。

项名称的名称解析允许项在模块或块中引用项的位置之前或之后定义。

有关项的作用域规则信息,请参阅项作用域

模块

Syntax
Module
      unsafe? mod IDENTIFIER ;
    | unsafe? mod IDENTIFIER {
        InnerAttribute*
        Item*
      }

模块是零个或多个的容器。

_模块项_是一个模块,用花括号括起来,命名,并以关键字 mod 为前缀。模块项在构成 crate 的模块树中引入一个新的、命名的模块。

模块可以任意嵌套。

模块示例:

#![allow(unused)]
fn main() {
mod math {
    type Complex = (f64, f64);
    fn sin(f: f64) -> f64 {
        /* ... */
      unimplemented!();
    }
    fn cos(f: f64) -> f64 {
        /* ... */
      unimplemented!();
    }
    fn tan(f: f64) -> f64 {
        /* ... */
      unimplemented!();
    }
}
}

模块在它们所在的模块或块的类型命名空间中定义。

在同一模块内的同一命名空间中定义多个同名项是错误的。有关限制和遮蔽行为的更多详细信息,请参阅作用域章节

unsafe 关键字在语法上允许出现在 mod 关键字之前,但在语义级别上被拒绝。这允许宏消费该语法并使用 unsafe 关键字,然后将其从词法单元流中移除。

模块源文件名

没有主体的模块从外部文件加载。当模块没有 path 属性时,文件的路径反映逻辑模块路径

祖先模块路径组件是目录,模块的内容在以模块名称加 .rs 扩展名命名的文件中。例如,以下模块结构可以具有此相应的文件系统结构:

模块路径文件系统路径文件内容
cratelib.rsmod util;
crate::utilutil.rsmod config;
crate::util::configutil/config.rs

模块文件名也可以是模块名称作为目录,内容在该目录中名为 mod.rs 的文件中。上面的示例可以 alternately 表示为 crate::util 的内容在名为 util/mod.rs 的文件中。不允许同时拥有 util.rsutil/mod.rs

Note

rustc 1.30 之前,使用 mod.rs 文件是加载具有嵌套子模块的模块的方式。鼓励使用新的命名约定,因为它更一致,并且避免在项目中有许多名为 mod.rs 的文件。

path 属性

用于加载外部文件模块的目录和文件可以通过 path 属性来影响。

对于不在内联模块块上的模块上的 path 属性,文件路径相对于源文件所在的目录。例如,以下代码片段将根据其位置使用所示的路径:

#[path = "foo.rs"]
mod c;
源文件c 的文件位置c 的模块路径
src/a/b.rssrc/a/foo.rscrate::a::b::c
src/a/mod.rssrc/a/foo.rscrate::a::c

对于内联模块块内的 path 属性,文件路径的相对位置取决于 path 所在的源文件的种类。“mod-rs” 源文件是根模块(如 lib.rsmain.rs)和文件名为 mod.rs 的模块。“non-mod-rs” 源文件是所有其他模块文件。mod-rs 文件中内联模块块内 path 属性的路径相对于 mod-rs 文件的目录,包括内联模块组件作为目录。对于 non-mod-rs 文件,除了路径以 non-mod-rs 模块名称的目录开头外,其余相同。例如,以下代码片段将根据其位置使用所示的路径:

mod inline {
    #[path = "other.rs"]
    mod inner;
}
源文件inner 的文件位置inner 的模块路径
src/a/b.rssrc/a/b/inline/other.rscrate::a::b::inline::inner
src/a/mod.rssrc/a/inline/other.rscrate::a::inline::inner

结合上述 path 属性在内联模块和嵌套模块上的规则的示例(适用于 mod-rs 和 non-mod-rs 文件):

#[path = "thread_files"]
mod thread {
    // Load the `local_data` module from `thread_files/tls.rs` relative to
    // this source file's directory.
    #[path = "tls.rs"]
    mod local_data;
}

模块上的属性

模块与所有项一样,接受外部属性。它们也接受内部属性:对于有主体的模块,在 { 之后,或者在源文件的开头,在可选的 BOM 和 shebang 之后。

在模块上有意义的内置属性是 cfgdeprecateddoclint 检查属性pathno_implicit_prelude。模块也接受宏属性。

外部 crate 声明

Syntax
ExternCrateextern crate CrateRef AsClause? ;

CrateRefIDENTIFIER | self

AsClauseas ( IDENTIFIER | _ )

_extern crate 声明_指定对外部 crate 的依赖。

然后,外部 crate 被绑定到声明作用域中作为给定的标识符,位于类型命名空间中。

此外,如果 extern crate 出现在 crate 根中,则 crate 名称也会被添加到外部 prelude中,使其自动在所有模块中处于作用域内。

as 子句可用于将导入的 crate 绑定到不同的名称。

外部 crate 在编译时被解析为特定的 soname,并且该 soname 的运行时链接需求被传递给链接器以在运行时加载。soname 在编译时通过扫描编译器的库路径并将提供的可选 crate_name 与外部 crate 编译时声明的 crate_name 属性进行匹配来解析。如果未提供 crate_name,则假定默认的 name 属性,等于 extern crate 声明中给出的标识符

可以导入 self crate,这会创建对当前 crate 的绑定。在这种情况下,必须使用 as 子句来指定要绑定到的名称。

三个 extern crate 声明的示例:

extern crate pcre;

extern crate std; // equivalent to: extern crate std as std;

extern crate std as ruststd; // linking to 'std' under another name

命名 Rust crate 时,不允许使用连字符。但是,Cargo 包可以使用它们。在这种情况下,当 Cargo.toml 未指定 crate 名称时,Cargo 会透明地将 - 替换为 _(有关更多详细信息,请参阅 RFC 940)。

这是一个示例:

// Importing the Cargo package hello-world
extern crate hello_world; // hyphen replaced with an underscore

下划线导入

外部 crate 依赖可以通过使用下划线形式 extern crate foo as _ 来声明,而不在作用域中绑定其名称。这对于只需要链接但从未被引用的 crate 可能很有用,并且将避免被报告为未使用。

macro_use 属性照常工作,并将宏名称导入到 macro_use prelude中。

no_link 属性 可以应用于 extern crate 项以阻止链接该 crate。

Note

这很有用,例如,当只需要 crate 的宏时。

Example

#[no_link]
extern crate other_crate;

other_crate::some_macro!();

no_link 属性使用 MetaWord 语法。

no_link 属性只能应用于 extern crate 声明。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

只有 extern crate 声明上的第一次使用 no_link 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

Use 声明

Syntax
UseDeclarationuse UseTree ;

UseTree
      ( SimplePath? :: )? *
    | ( SimplePath? :: )? { ( UseTree ( , UseTree )* ,? )? }
    | SimplePath ( as ( IDENTIFIER | _ ) )?

use 声明_创建一个或多个本地名称绑定,与某个其他路径同义。通常,use 声明用于缩短引用模块项所需的路径。这些声明可能出现在模块中,通常在顶部。use 声明有时也称为_导入,或者,如果是公开的,则称为_重导出_。

Use 声明支持许多便捷快捷方式:

  • 使用大括号语法 use a::b::{c, d, e::f, g::h::i}; 同时绑定具有公共前缀的路径列表
  • 使用 self 关键字同时绑定具有公共前缀的路径列表及其公共父模块,例如 use a::b::{self, c, d::e};
  • 使用语法 use p::q::r as x; 将目标名称重新绑定为新的本地名称。这也可以与最后两个特性一起使用:use a::b::{self as ab, c as abc}
  • 使用星号通配符语法 use a::b::*; 绑定匹配给定前缀的所有路径。
  • 多次嵌套前面特性的组,例如 use a::b::{self as ab, c, d::{*, e::f}};

use 声明的示例:

use std::collections::hash_map::{self, HashMap};

fn foo<T>(_: T){}
fn bar(map1: HashMap<String, usize>, map2: hash_map::HashMap<String, usize>){}

fn main() {
    // use declarations can also exist inside of functions
    use std::option::Option::{Some, None};

    // Equivalent to 'foo(vec![std::option::Option::Some(1.0f64),
    // std::option::Option::None]);'
    foo(vec![Some(1.0f64), None]);

    // Both `hash_map` and `HashMap` are in scope.
    let map1 = HashMap::new();
    let map2 = hash_map::HashMap::new();
    bar(map1, map2);
}

use 可见性

与项一样,use 声明默认对包含模块是私有的。也与项一样,如果由 pub 关键字限定,则 use 声明可以是公开的。这样的 use 声明用于_重导出_名称。因此,公开的 use 声明可以将某些公开名称_重定向_到不同的目标定义:甚至是不同模块内的具有私有规范路径的定义。

如果此类重定向的序列形成循环或无法明确解析,则它们表示编译时错误。

重导出的示例:

mod quux {
    pub use self::foo::{bar, baz};
    pub mod foo {
        pub fn bar() {}
        pub fn baz() {}
    }
}

fn main() {
    quux::bar();
    quux::baz();
}

在此示例中,模块 quux 重导出了在 foo 中定义的两个公开名称。

use 路径

use 项中允许的路径遵循 SimplePath 语法,类似于表达式中可能使用的路径。它们可以为以下内容创建绑定:

它们不能导入关联项泛型参数局部变量、带有 Self 的路径或工具属性。更多限制如下所述。

use 将为导入实体的所有命名空间创建绑定,但 self 导入仅从类型命名空间导入(如下所述)。例如,以下说明了在两个命名空间中为同一名称创建绑定:

#![allow(unused)]
fn main() {
mod stuff {
    pub struct Foo(pub i32);
}

// Imports the `Foo` type and the `Foo` constructor.
use stuff::Foo;

fn example() {
    let ctor = Foo; // Uses `Foo` from the value namespace.
    let x: Foo = ctor(123); // Uses `Foo` From the type namespace.
}
}

2018 Edition differences

在 2015 版本中,use 路径相对于 crate 根。例如:

mod foo {
    pub mod example { pub mod iter {} }
    pub mod baz { pub fn foobaz() {} }
}
mod bar {
    // Resolves `foo` from the crate root.
    use foo::example::iter;
    // The `::` prefix explicitly resolves `foo`
    // from the crate root.
    use ::foo::baz::foobaz;
}

fn main() {}

2015 版本不允许 use 声明引用外部 prelude。因此,在 2015 版本中仍然需要 extern crate 声明来在 use 声明中引用外部 crate。从 2018 版本开始,use 声明可以像 extern crate 一样指定外部 crate 依赖。

as 重命名

as 关键字可用于更改导入实体的名称。例如:

#![allow(unused)]
fn main() {
// Creates a non-public alias `bar` for the function `foo`.
use inner::foo as bar;

mod inner {
    pub fn foo() {}
}
}

大括号语法

大括号可用于路径的最后一段,以从前一段导入多个实体,或者,如果没有前一段,则从当前作用域导入。大括号可以嵌套,创建路径树,其中每个分段组在逻辑上与其父级组合以创建完整路径。

#![allow(unused)]
fn main() {
// Creates bindings to:
// - `std::collections::BTreeSet`
// - `std::collections::hash_map`
// - `std::collections::hash_map::HashMap`
use std::collections::{BTreeSet, hash_map::{self, HashMap}};
}

空大括号不导入任何内容,但会验证前导路径是否可访问。

2018 Edition differences

在 2015 版本中,路径相对于 crate 根,因此像 use {foo, bar}; 这样的导入将从 crate 根导入名称 foobar,而从 2018 版本开始,这些名称相对于当前作用域。

self 导入

关键字 self 可在大括号语法内使用,以在其自身名称下创建父实体的绑定。

mod stuff {
    pub fn foo() {}
    pub fn bar() {}
}
mod example {
    // Creates a binding for `stuff` and `foo`.
    use crate::stuff::{self, foo};
    pub fn baz() {
        foo();
        stuff::bar();
    }
}
fn main() {}

Note

self 也可用作路径的第一段。self 作为第一段和在 use 大括号内的使用在逻辑上是相同的;它表示父段的当前模块,或者如果没有父段,则表示当前模块。有关前导 self 的含义的更多信息,请参阅路径章节中的 self

self 可以作为 use 路径的最后一段出现,前面是 ::。形式为 P::self 的路径等同于 P::{self}P::self as name 等同于 P::{self as name}

mod m {
    pub enum E { V1, V2 }
}
use m::self as _; // Equivalent to `use m::{self as _};`.
use m::E::self; // Equivalent to `use m::E::{self};`.
fn main() {}

Note

有关前面路径的限制,请参阅 paths.qualifiers.mod-self.trailing

当在大括号语法内使用 self 时,大括号组前面的路径必须解析为模块枚举trait

mod m {
    pub enum E { V1, V2 }
    pub trait Tr { fn f(&self); }
}
use m::{self as _}; // OK: Modules can be parents of `self`.
use m::E::{self, V1}; // OK: Enums can be parents of `self`.
use m::Tr::{self}; // OK: Traits can be parents of `self`.
fn main() {}
struct S {}
use S::{self as _}; // ERROR: Structs cannot be parents of `self`.
fn main() {}

self 仅从父实体的类型命名空间创建绑定。例如,在以下示例中,仅导入了 foo 模块:

mod bar {
    pub mod foo {}
    pub fn foo() {}
}

// This only imports the module `foo`. The function `foo` lives in
// the value namespace and is not imported.
use bar::foo::{self};

fn main() {
    foo(); //~ ERROR `foo` is a module
}

Glob 导入

* 字符可用作 use 路径的最后一段,以导入前一段实体中的所有可导入实体。例如:

#![allow(unused)]
fn main() {
// Creates a non-public alias to `bar`.
use foo::*;

mod foo {
    fn i_am_private() {}
    enum Example {
        V1,
        V2,
    }
    pub fn bar() {
        // Creates local aliases to `V1` and `V2`
        // of the `Example` enum.
        use Example::*;
        let x = V1;
    }
}
}

项和命名导入允许在同一命名空间中遮蔽 glob 导入的名称。也就是说,如果同一命名空间中已有另一个项定义的名称,则 glob 导入将被遮蔽。例如:

#![allow(unused)]
fn main() {
// This creates a binding to the `clashing::Foo` tuple struct
// constructor, but does not import its type because that would
// conflict with the `Foo` struct defined here.
//
// Note that the order of definition here is unimportant.
use clashing::*;
struct Foo {
    field: f32,
}

fn do_stuff() {
    // Uses the constructor from `clashing::Foo`.
    let f1 = Foo(123);
    // The struct expression uses the type from
    // the `Foo` struct defined above.
    let f2 = Foo { field: 1.0 };
    // `Bar` is also in scope due to the glob import.
    let z = Bar {};
}

mod clashing {
    pub struct Foo(pub i32);
    pub struct Bar {}
}
}

Note

有关不允许遮蔽的区域,请参阅名称解析歧义

* 不能用作第一段或中间段。

* 不能用于将模块的内容导入到自身(如 use self::*;)。

2018 Edition differences

在 2015 版本中,路径相对于 crate 根,因此像 use *; 这样的导入是有效的,它意味着从 crate 根导入所有内容。这不能在 crate 根本身中使用。

下划线导入

可以使用下划线形式 use path as _ 导入项而不绑定到名称。这对于导入 trait 特别有用,以便可以使用其方法而不导入 trait 的符号,例如,如果 trait 的符号可能与另一个符号冲突。另一个示例是链接外部 crate 而不导入其名称。

星号 glob 导入将以不可命名的形式导入使用 _ 导入的项。

mod foo {
    pub trait Zoo {
        fn zoo(&self) {}
    }

    impl<T> Zoo for T {}
}

use self::foo::Zoo as _;
struct Zoo;  // Underscore import avoids name conflict with this item.

fn main() {
    let z = Zoo;
    z.zoo();
}

唯一的、不可命名的符号在宏展开后创建,以便宏可以安全地发出对 _ 导入的多个引用。例如,以下不应产生错误:

#![allow(unused)]
fn main() {
macro_rules! m {
    ($item: item) => { $item $item }
}

m!(use std as _;);
// This expands to:
// use std as _;
// use std as _;
}

限制

以下是有效 use 声明的限制。

使用 crate 导入当前 crate 时,必须使用 as 来定义绑定名称。

Example

#![allow(unused)]
fn main() {
use crate as root;
use crate::{self as root2};

// Not allowed:
// use crate;
// use crate::{self};
}

在宏转录器中使用 $crate 导入当前 crate 时,必须使用 as 来定义绑定名称。

Example

#![allow(unused)]
fn main() {
macro_rules! import_crate_root {
    () => {
        use $crate as my_crate;
        use $crate::{self as my_crate2};
    };
}
}

使用 self 导入当前模块时,必须使用 as 来定义绑定名称。

Example

#![allow(unused)]
fn main() {
use {self as this_module};
use self as this_module2;
use self::{self as this_module3};

// Not allowed:
// use {self};
// use self;
// use self::{self};
}

使用 super 导入父模块时,必须使用 as 来定义绑定名称。

Example

#![allow(unused)]
fn main() {
mod a {
    mod b {
        use super as parent;
        use super::{self as parent2};
        use self::super as parent3;
        use super::super as grandparent;
        use super::super::{self as grandparent2};

        // Not allowed:
        // use super;
        // use super::{self};
        // use self::super;
        // use super::super;
        // use super::super::{self};
    }
}
}

作为外部 prelude:: 不能被导入。

Example

#![allow(unused)]
fn main() {
use ::{self as root}; //~ Error
}

2018 Edition differences

在 2015 版本中,前缀 :: 指的是 crate 根,因此 use ::{self as root}; 是允许的,因为它与 use crate::{self as root}; 相同。从 2018 版本开始,:: 前缀指的是外部 prelude,不能直接导入。

#![allow(unused)]
fn main() {
use ::{self as root}; //~ Ok
}

与任何项定义一样,use 导入不能在同一模块或块中的同一命名空间中创建同名的重复绑定。

use 路径不能通过类型别名引用枚举变体。

Example

#![allow(unused)]
fn main() {
enum MyEnum {
  MyVariant
}
type TypeAlias = MyEnum;

use MyEnum::MyVariant; //~ OK
use TypeAlias::MyVariant; //~ ERROR
}

函数

Syntax
Function
    FunctionQualifiers fn IDENTIFIER GenericParams?
        ( FunctionParameters? )
        FunctionReturnType? WhereClause?
        ( BlockExpression | ; )

FunctionQualifiersconst? async?1 ItemSafety?2 ( extern Abi? )?

ItemSafetysafe3 | unsafe

AbiSTRING_LITERAL | RAW_STRING_LITERAL

FunctionParameters
      SelfParam ,?
    | ( SelfParam , )? FunctionParam ( , FunctionParam )* ,?

SelfParamOuterAttribute* ( ShorthandSelf | TypedSelf )

ShorthandSelf → ( & | & Lifetime )? mut? self

TypedSelfmut? self : Type

FunctionParamOuterAttribute* ( FunctionParamPattern | ... | Type4 )

FunctionParamPatternPatternNoTopAlt : ( Type | ... )

FunctionReturnType-> Type

函数_由一个(即函数的_主体)、一个名称、一组参数和一个输出类型组成。除了名称外,所有这些都是可选的。

函数使用关键字 fn 声明,该关键字在其所在的模块或块的值命名空间中定义给定名称。

函数可以声明一组输入变量作为参数,调用者通过这些参数将参数传递给函数,以及函数完成后将返回给调用者的值的输出类型

如果未明确说明输出类型,则它是单元类型

当被引用时,_函数_产生相应零大小函数项类型的一等,当被调用时,该值计算为对函数的直接调用。

例如,这是一个简单的函数:

#![allow(unused)]
fn main() {
fn answer_to_life_the_universe_and_everything() -> i32 {
    return 42;
}
}

safe 函数在语义上仅允许在 extern中使用。

函数参数

函数参数是不可反驳的模式,因此在没有 else 的 let 绑定中有效的任何模式作为参数也有效:

#![allow(unused)]
fn main() {
fn first((value, _): (i32, i32)) -> i32 { value }
}

如果第一个参数是 SelfParam,则表示该函数是方法

具有 self 参数的函数只能作为 trait实现中的关联函数出现。

带有 ... 词法单元的参数表示可变参数函数,只能用作外部块函数的最后一个参数。可变参数可以有一个可选的标识符,例如 args: ...

函数主体

函数的主体块在概念上被包装在另一个块中,该块首先绑定参数模式,然后 return 函数主体的值。这意味着,如果计算了块的尾部表达式,它最终将返回给调用者。像往常一样,函数主体内的显式 return 表达式如果到达,将短路该隐式返回。

例如,上面的函数的行为就像它是这样编写的:

// argument_0 is the actual first argument passed from the caller
let (value, _) = argument_0;
return {
    value
};

没有主体块的函数以分号终止。此形式只能出现在 trait外部块中。

泛型函数

泛型函数_允许其签名中出现一个或多个_参数化类型。每个类型参数必须在函数名后面的尖括号括起来的逗号分隔列表中显式声明。

#![allow(unused)]
fn main() {
// foo is generic over A and B

fn foo<A, B>(x: A, y: B) {
}
}

在函数签名和主体内部,类型参数的名称可以用作类型名称。

可以为类型参数指定 Trait 约束,以允许在该类型的值上调用该 trait 的方法。这是使用 where 语法指定的:

#![allow(unused)]
fn main() {
use std::fmt::Debug;
fn foo<T>(x: T) where T: Debug {
}
}

当引用泛型函数时,其类型根据引用的上下文进行实例化。例如,调用此处的 foo 函数:

#![allow(unused)]
fn main() {
use std::fmt::Debug;

fn foo<T>(x: &[T]) where T: Debug {
    // details elided
}

foo(&[1, 2]);
}

将使用 i32 实例化类型参数 T

类型参数也可以在函数名后面的尾部路径组件中显式提供。如果没有足够的上下文来确定类型参数,这可能是必要的。例如,mem::size_of::<u32>() == 4

Extern 函数限定符

extern 函数限定符允许提供可以使用特定 ABI 调用的函数_定义_:

extern "ABI" fn foo() { /* ... */ }

这些通常与外部块项结合使用,后者提供函数_声明_,可用于调用函数而无需提供其_定义_:

unsafe extern "ABI" {
  unsafe fn foo(); /* no body */
  safe fn bar(); /* no body */
}
unsafe { foo() };
bar();

当函数项中的 FunctionQualifiers 省略 "extern" Abi?* 时,将分配 ABI "Rust"。例如:

#![allow(unused)]
fn main() {
fn foo() {}
}

等同于:

#![allow(unused)]
fn main() {
extern "Rust" fn foo() {}
}

函数可以被外部代码调用,使用与 Rust 不同的 ABI 允许,例如,提供可以从 C 等其他编程语言调用的函数:

#![allow(unused)]
fn main() {
// Declares a function with the "C" ABI
extern "C" fn new_i32() -> i32 { 0 }

// Declares a function with the "stdcall" ABI
#[cfg(any(windows, target_arch = "x86"))]
extern "stdcall" fn new_i32_stdcall() -> i32 { 0 }
}

外部块一样,当使用 extern 关键字且省略 "ABI" 时,使用的 ABI 默认为 "C"。也就是说:

#![allow(unused)]
fn main() {
extern fn new_i32() -> i32 { 0 }
let fptr: extern fn() -> i32 = new_i32;
}

等同于:

#![allow(unused)]
fn main() {
extern "C" fn new_i32() -> i32 { 0 }
let fptr: extern "C" fn() -> i32 = new_i32;
}

展开

大多数 ABI 字符串有两种变体,一种带有 -unwind 后缀,一种不带。Rust ABI 始终允许展开,因此没有 Rust-unwind ABI。ABI 的选择以及运行时 panic 处理器决定了展开函数时的行为。

下表表示展开操作到达每种类型 ABI 边界(使用相应 ABI 字符串的函数声明或定义)时的行为。请注意,Rust 运行时不受任何完全在另一种语言的运行时内发生的展开的影响,也不能对其产生影响,即那些被抛出和捕获而未到达 Rust ABI 边界的展开。

panic-unwind 列指的是通过 panic! 宏和类似标准库机制进行的 panicking,以及导致 panic 的任何其他 Rust 操作,例如越界数组索引或整数溢出。

“unwinding” ABI 类别指的是 "Rust"(未标记 extern 的 Rust 函数的隐式 ABI)、"C-unwind" 以及名称中带有 -unwind 的任何其他 ABI。“non-unwinding” ABI 类别指的是所有其他 ABI 字符串,包括 "C""stdcall"

本机展开是按目标定义的。在支持抛出和捕获 C++ 异常的平台上,它指的是用于实现此功能的某些平台实现了一种称为“强制展开”的展开形式;Windows 上的 longjmpglibc 中的 pthread_exit 就是这样实现的。强制展开被明确排除在表中的“本机展开“列之外。

panic 运行时ABIpanic-unwind本机展开(非强制)
panic=unwindunwinding展开展开
panic=unwindnon-unwinding中止(见下文注释)[未定义行为]
panic=abortunwindingpanic 中止而不展开中止
panic=abortnon-unwindingpanic 中止而不展开[未定义行为]

使用 panic=unwind 时,当 panic 被 non-unwinding ABI 边界转换为中止时,要么不运行任何析构器(Drop 调用),要么运行直到 ABI 边界的所有析构器。未指定这两种行为中的哪一种会发生。

有关跨 FFI 边界展开的其他注意事项和限制,请参阅 Panic 文档中的相关部分

Const 函数

有关 const 函数的定义,请参阅 const 函数

异步函数

函数可以被限定为 async,这也可以与 unsafe 限定符组合:

#![allow(unused)]
fn main() {
async fn regular_example() { }
async unsafe fn unsafe_example() { }
}

异步函数在调用时不执行任何工作:相反,它们将其参数捕获到一个 future 中。当被轮询时,该 future 将执行函数的主体。

异步函数大致等同于返回 impl Future 的函数,其主体为 async move

#![allow(unused)]
fn main() {
// Source
async fn example(x: &str) -> usize {
    x.len()
}
}

大致等同于:

#![allow(unused)]
fn main() {
use std::future::Future;
// Desugared
fn example<'a>(x: &'a str) -> impl Future<Output = usize> + 'a {
    async move { x.len() }
}
}

实际的脱糖更复杂:

  • 脱糖中的返回类型被假定为捕获 async fn 声明中的所有生命周期参数。这可以在上面的脱糖示例中看到,它显式地比 'a 活得更久,因此捕获了 'a
  • 主体中的 async move捕获所有函数参数,包括那些未使用或绑定到 _ 模式的参数。这确保函数参数以与函数不是 async 时相同的顺序被丢弃,只是丢弃发生在返回的 future 被完全 await 时。

有关 async 效果的更多信息,请参阅 async

2018 Edition differences

异步函数仅从 Rust 2018 版本开始可用。

组合 asyncunsafe

声明一个既是 async 又是 unsafe 的函数是合法的。生成的函数调用是不安全的,并且(像任何异步函数一样)返回一个 future。这个 future 只是一个普通的 future,因此不需要 unsafe 上下文来“await“它:

#![allow(unused)]
fn main() {
// Returns a future that, when awaited, dereferences `x`.
//
// Soundness condition: `x` must be safe to dereference until
// the resulting future is complete.
async unsafe fn unsafe_example(x: *const i32) -> i32 {
  *x
}

async fn safe_example() {
    // An `unsafe` block is required to invoke the function initially:
    let p = 22;
    let future = unsafe { unsafe_example(&p) };

    // But no `unsafe` block required here. This will
    // read the value of `p`:
    let q = future.await;
}
}

请注意,此行为是脱糖为返回 impl Future 的函数的结果 – 在这种情况下,我们脱糖到的函数是一个 unsafe 函数,但返回值保持不变。

Unsafe 在异步函数上的使用方式与其他函数完全相同:它表示该函数对其调用者施加了一些额外的义务以确保健全性。与任何其他 unsafe 函数一样,这些条件可能超出初始调用本身 – 例如,在上面的代码片段中,unsafe_example 函数接受指针 x 作为参数,然后(当 await 时)解引用该指针。这意味着 x 必须在 future 完成执行之前有效,调用者有责任确保这一点。

函数上的属性

函数上允许使用外部属性。在其主体内的 { 之后直接允许使用内部属性

此示例显示了函数上的内部属性。该函数仅用“Example“一词记录。

#![allow(unused)]
fn main() {
fn documented() {
    #![doc = "Example"]
}
}

Note

除了 lint 之外,习惯上只在函数项上使用外部属性。

在函数上有意义的属性是:

函数参数上的属性

函数参数上允许使用外部属性,允许的内置属性仅限于 cfgcfg_attrallowwarndenyforbid

#![allow(unused)]
fn main() {
fn len(
    #[cfg(windows)] slice: &[u16],
    #[cfg(not(windows))] slice: &[u8],
) -> usize {
    slice.len()
}
}

应用于项的过程宏属性使用的惰性辅助属性也是允许的,但请注意不要将这些惰性属性包含在最终的 TokenStream 中。

例如,以下代码定义了一个惰性的 some_inert_attribute 属性,该属性未在任何地方正式定义,some_proc_macro_attribute 过程宏负责检测其存在并将其从输出词法单元流中移除。

#[some_proc_macro_attribute]
fn foo_oof(#[some_inert_attribute] arg: u8) {
}

  1. async 限定符在 2015 版本中不允许使用。

  2. 与 Rust 2024 之前的版本相关:在 extern 块内,safeunsafe 函数限定符仅在 extern 被限定为 unsafe 时才允许使用。

  3. safe 函数限定符在语义上仅允许在 extern 块内使用。

  4. 仅带类型的函数参数在 2015 版本中仅允许在 trait 项的关联函数中使用。

类型别名

Syntax
TypeAlias
    type IDENTIFIER GenericParams? ( : Bounds )?
        WhereClause?
        ( = Type WhereClause? )? ;

_类型别名_在其所在的模块或块的类型命名空间中为现有类型定义新名称。类型别名使用关键字 type 声明。每个值都有一个单一的、特定的类型,但可以实现几个不同的 trait,并且可以与几个不同的类型约束兼容。

例如,以下将类型 Point 定义为类型 (u8, u8)(无符号 8 位整数对的类型)的同义词:

#![allow(unused)]
fn main() {
type Point = (u8, u8);
let p: Point = (41, 68);
}

元组结构体或单元结构体的类型别名不能用于限定该类型的构造函数:

#![allow(unused)]
fn main() {
struct MyStruct(u32);

use MyStruct as UseAlias;
type TypeAlias = MyStruct;

let _ = UseAlias(5); // OK
let _ = TypeAlias(5); // Doesn't work
}

类型别名在不作为关联类型使用时,必须包含一个 Type,并且不能包含 Bounds

类型别名在 trait 中作为关联类型使用时,不能包含 Type 规范,但可以包含 Bounds

类型别名在 trait impl 中作为关联类型使用时,必须包含 Type 规范,并且不能包含 Bounds

trait impl 中的类型别名的等号之前使用 where 子句(如 type TypeAlias<T> where T: Foo = Bar<T>)已弃用。等号之后的 where 子句(如 type TypeAlias<T> = Bar<T> where T: Foo)是首选。

结构体

_结构体_是使用关键字 struct 定义的具名结构体类型

结构体声明在其所在的模块或块的类型命名空间中定义给定名称。

struct 项及其使用的示例:

#![allow(unused)]
fn main() {
struct Point {x: i32, y: i32}
let p = Point {x: 10, y: 11};
let px: i32 = p.x;
}

_元组结构体_是具名元组类型,也使用关键字 struct 定义。除了定义类型外,它还在值命名空间中定义同名的构造函数。构造函数是一个函数,可以调用它来创建结构体的新实例。例如:

#![allow(unused)]
fn main() {
struct Point(i32, i32);
let p = Point(10, 11);
let px: i32 = match p { Point(x, _) => x };
}

_类单元结构体_是没有任何字段的结构体,通过完全省略字段列表来定义。这样的结构体隐式定义了一个具有相同名称的类型常量。例如:

#![allow(unused)]
fn main() {
struct Cookie;
let c = [Cookie, Cookie {}, Cookie, Cookie {}];
}

等同于

#![allow(unused)]
fn main() {
struct Cookie {}
const Cookie: Cookie = Cookie {};
let c = [Cookie, Cookie {}, Cookie, Cookie {}];
}

结构体的精确内存布局未指定。可以使用 repr 属性指定特定布局。

枚举

枚举,也称为 enum,是具名枚举类型以及一组构造函数的同时定义,可用于创建或模式匹配相应枚举类型的值。

枚举使用关键字 enum 声明。

enum 声明在其所在的模块或块的类型命名空间中定义枚举类型。

enum 项及其使用的示例:

#![allow(unused)]
fn main() {
enum Animal {
    Dog,
    Cat,
}

let mut a: Animal = Animal::Dog;
a = Animal::Cat;
}

枚举构造函数可以有命名或未命名的字段:

#![allow(unused)]
fn main() {
enum Animal {
    Dog(String, f64),
    Cat { name: String, weight: f64 },
}

let mut a: Animal = Animal::Dog("Cocoa".to_string(), 37.2);
a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 };
}

在此示例中,Cat 是一个_类结构体枚举变体_,而 Dog 只是称为枚举变体。

没有构造函数包含字段的枚举称为 无字段枚举。例如,这是一个无字段枚举:

#![allow(unused)]
fn main() {
enum Fieldless {
    Tuple(),
    Struct{},
    Unit,
}
}

如果无字段枚举仅包含单元变体,则该枚举称为 纯单元枚举。例如:

#![allow(unused)]
fn main() {
enum Enum {
    Foo = 3,
    Bar = 2,
    Baz = 1,
}
}

变体构造函数类似于结构体定义,可以通过枚举名称的路径引用,包括在 use 声明中。

每个变体在类型命名空间中定义其类型,但该类型不能用作类型说明符。类元组和类单元变体也在值命名空间中定义构造函数。

类结构体变体可以使用结构体表达式实例化。

类元组变体可以使用调用表达式结构体表达式实例化。

类单元变体可以使用路径表达式结构体表达式实例化。例如:

#![allow(unused)]
fn main() {
enum Examples {
    UnitLike,
    TupleLike(i32),
    StructLike { value: i32 },
}

use Examples::*; // Creates aliases to all variants.
let x = UnitLike; // Path expression of the const item.
let x = UnitLike {}; // Struct expression.
let y = TupleLike(123); // Call expression.
let y = TupleLike { 0: 123 }; // Struct expression using integer field names.
let z = StructLike { value: 123 }; // Struct expression.
}

判别值

每个枚举实例都有一个_判别值_:一个逻辑上与之关联的整数,用于确定它持有的是哪个变体。

Rust 表示下,判别值被解释为 isize 值。但是,编译器允许在其实际内存布局中使用更小的类型(或区分变体的其他方式)。

分配判别值

显式判别值

在两种情况下,变体的判别值可以通过在变体名称后跟 =常量表达式来显式设置:

  1. 如果枚举是“纯单元“的。
  1. 如果使用了原始表示。例如:

    #![allow(unused)]
    fn main() {
    #[repr(u8)]
    enum Enum {
        Unit = 3,
        Tuple(u16),
        Struct {
            a: u8,
            b: u16,
        } = 1,
    }
    }

隐式判别值

如果未指定变体的判别值,则将其设置为声明中前一个变体的判别值加一。如果声明中第一个变体的判别值未指定,则将其设置为零。

#![allow(unused)]
fn main() {
enum Foo {
    Bar,            // 0
    Baz = 123,      // 123
    Quux,           // 124
}

let baz_discriminant = Foo::Baz as u32;
assert_eq!(baz_discriminant, 123);
}

限制

当两个变体共享相同的判别值时是错误的。

#![allow(unused)]
fn main() {
enum SharedDiscriminantError {
    SharedA = 1,
    SharedB = 1,
}

enum SharedDiscriminantError2 {
    Zero,       // 0
    One,        // 1
    OneToo = 1, // 1 (collision with previous!)
}
}

当前一个判别值是判别值大小的最大值时,具有未指定的判别值也是错误的。

#![allow(unused)]
fn main() {
#[repr(u8)]
enum OverflowingDiscriminantError {
    Max = 255,
    MaxPlusOne, // Would be 256, but that overflows the enum.
}

#[repr(u8)]
enum OverflowingDiscriminantError2 {
    MaxMinusOne = 254, // 254
    Max,               // 255
    MaxPlusOne,        // Would be 256, but that overflows the enum.
}
}

显式枚举判别值初始化器不能使用封闭枚举的泛型参数。

#![allow(unused)]
fn main() {
#[repr(u32)]
enum E<'a, T, const N: u32> {
    Lifetime(&'a T) = {
        let a: &'a (); // ERROR.
        1
    },
    Type(T) = {
        let x: T; // ERROR.
        2
    },
    Const = N, // ERROR.
}
}

访问判别值

通过 mem::discriminant

std::mem::discriminant 返回对枚举值判别值的不透明引用,可以进行比较。这不能用于获取判别值的值。

类型转换

如果枚举是纯单元的(没有元组和结构体变体),则其判别值可以通过数值类型转换直接访问;例如:

#![allow(unused)]
fn main() {
enum Enum {
    Foo,
    Bar,
    Baz,
}

assert_eq!(0, Enum::Foo as isize);
assert_eq!(1, Enum::Bar as isize);
assert_eq!(2, Enum::Baz as isize);
}

无字段枚举如果没有显式判别值,或者只有单元变体是显式的,则可以进行类型转换。

#![allow(unused)]
fn main() {
enum Fieldless {
    Tuple(),
    Struct{},
    Unit,
}

assert_eq!(0, Fieldless::Tuple() as isize);
assert_eq!(1, Fieldless::Struct{} as isize);
assert_eq!(2, Fieldless::Unit as isize);

#[repr(u8)]
enum FieldlessWithDiscriminants {
    First = 10,
    Tuple(),
    Second = 20,
    Struct{},
    Unit,
}

assert_eq!(10, FieldlessWithDiscriminants::First as u8);
assert_eq!(11, FieldlessWithDiscriminants::Tuple() as u8);
assert_eq!(20, FieldlessWithDiscriminants::Second as u8);
assert_eq!(21, FieldlessWithDiscriminants::Struct{} as u8);
assert_eq!(22, FieldlessWithDiscriminants::Unit as u8);
}

指针类型转换

如果枚举指定了原始表示,则可以通过 unsafe 指针类型转换可靠地访问判别值:

#![allow(unused)]
fn main() {
#[repr(u8)]
enum Enum {
    Unit,
    Tuple(bool),
    Struct{a: bool},
}

impl Enum {
    fn discriminant(&self) -> u8 {
        unsafe { *(self as *const Self as *const u8) }
    }
}

let unit_like = Enum::Unit;
let tuple_like = Enum::Tuple(true);
let struct_like = Enum::Struct{a: false};

assert_eq!(0, unit_like.discriminant());
assert_eq!(1, tuple_like.discriminant());
assert_eq!(2, struct_like.discriminant());
}

零变体枚举

具有零个变体的枚举称为零变体枚举。由于它们没有有效值,因此无法实例化。

#![allow(unused)]
fn main() {
enum ZeroVariants {}
}

零变体枚举等同于永不类型,但它们不能被强制转换为其他类型。

#![allow(unused)]
fn main() {
enum ZeroVariants {}
let x: ZeroVariants = panic!();
let y: u32 = x; // mismatched type error
}

变体可见性

枚举变体在语法上允许使用 Visibility 标注,但在验证枚举时会被拒绝。这允许在使用它们的不同上下文中使用统一的语法解析项。

#![allow(unused)]
fn main() {
macro_rules! mac_variant {
    ($vis:vis $name:ident) => {
        enum $name {
            $vis Unit,

            $vis Tuple(u8, u16),

            $vis Struct { f: u8 },
        }
    }
}

// Empty `vis` is allowed.
mac_variant! { E }

// This is allowed, since it is removed before being validated.
#[cfg(false)]
enum E {
    pub U,
    pub(crate) T(u8),
    pub(super) T { f: String },
}
}

联合体

Syntax
Union
    union IDENTIFIER GenericParams? WhereClause? { StructFields? }

联合体声明使用与结构体声明相同的语法,只是用 union 代替 struct

联合体声明在其所在的模块或块的类型命名空间中定义给定名称。

#![allow(unused)]
fn main() {
#[repr(C)]
union MyUnion {
    f1: u32,
    f2: f32,
}
}

联合体的关键属性是联合体的所有字段共享公共存储。因此,对联合体一个字段的写入可以覆盖其其他字段,联合体的大小由其最大字段的大小决定。

联合体字段类型仅限于以下类型子集:

  • Copy 类型
  • 引用(&T&mut T,对于任意 T
  • ManuallyDrop<T>(对于任意 T
  • 仅包含允许的联合体字段类型的元组和数组

此限制特别确保联合体字段永远不需要被丢弃。与结构体和枚举一样,可以为联合体实现 impl Drop 来手动定义丢弃时发生的事情。

编译器不接受没有任何字段的联合体,但宏可以接受。

联合体的初始化

可以使用与结构体类型相同的语法创建联合体类型的值,只是它必须指定恰好一个字段:

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }

let u = MyUnion { f1: 1 };
}

上面的表达式创建了 MyUnion 类型的值,并使用字段 f1 初始化存储。可以使用与结构体字段相同的语法访问联合体:

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }

let u = MyUnion { f1: 1 };
let f = unsafe { u.f1 };
}

读取和写入联合体字段

联合体没有“活动字段“的概念。相反,每次联合体访问只是将存储解释为用于访问的字段的类型。

读取联合体字段会读取联合体在字段类型处的位。

字段可能具有非零偏移量(使用 C 表示时除外);在这种情况下,读取从字段偏移量开始的位

程序员有责任确保数据在字段类型处有效。未能这样做会导致未定义行为。例如,从布尔类型的字段读取值 3 是未定义行为。实际上,使用 C 表示写入然后从联合体读取类似于从用于写入的类型到用于读取的类型的 transmute

因此,所有联合体字段的读取都必须放在 unsafe 块中:

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
let u = MyUnion { f1: 1 };

unsafe {
    let f = u.f1;
}
}

通常,使用联合体的代码将围绕不安全的联合体字段访问提供安全包装器。

相比之下,写入联合体字段是安全的,因为它们只是覆盖任意数据,但不会导致未定义行为。(请注意,联合体字段类型永远不会有丢弃粘合,因此联合体字段写入永远不会隐式丢弃任何内容。)

联合体上的模式匹配

访问联合体字段的另一种方式是使用模式匹配。

联合体字段上的模式匹配使用与结构体模式相同的语法,只是模式必须指定恰好一个字段。

由于模式匹配类似于使用特定字段读取联合体,因此也必须将其放在 unsafe 块中。

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }

fn f(u: MyUnion) {
    unsafe {
        match u {
            MyUnion { f1: 10 } => { println!("ten"); }
            MyUnion { f2 } => { println!("{}", f2); }
        }
    }
}
}

模式匹配可以将联合体匹配为更大结构的字段。特别是,当通过 FFI 使用 Rust 联合体实现 C 标记联合体时,这允许同时匹配标记和相应的字段:

#![allow(unused)]
fn main() {
#[repr(u32)]
enum Tag { I, F }

#[repr(C)]
union U {
    i: i32,
    f: f32,
}

#[repr(C)]
struct Value {
    tag: Tag,
    u: U,
}

fn is_zero(v: Value) -> bool {
    unsafe {
        match v {
            Value { tag: Tag::I, u: U { i: 0 } } => true,
            Value { tag: Tag::F, u: U { f: num } } if num == 0.0 => true,
            _ => false,
        }
    }
}
}

联合体字段的引用

由于联合体字段共享公共存储,获得对联合体一个字段的写访问权可以给予对其所有剩余字段的写访问权。

借用检查规则必须调整以考虑这一事实。因此,如果联合体的一个字段被借用,则其所有剩余字段在相同的生命周期内也被借用。

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
// ERROR: cannot borrow `u` (via `u.f2`) as mutable more than once at a time
fn test() {
    let mut u = MyUnion { f1: 1 };
    unsafe {
        let b1 = &mut u.f1;
//                    ---- first mutable borrow occurs here (via `u.f1`)
        let b2 = &mut u.f2;
//                    ^^^^ second mutable borrow occurs here (via `u.f2`)
        *b1 = 5;
    }
//  - first borrow ends here
    assert_eq!(unsafe { u.f1 }, 5);
}
}

如您所见,在许多方面(除了布局、安全性和所有权),联合体的行为与结构体完全相同,这主要是由于从结构体继承了其语法形状。对于 Rust 语言的许多未提及方面(如隐私、名称解析、类型推断、泛型、trait 实现、固有实现、一致性、模式检查等等等等)也是如此。

常量项

Syntax
ConstantItem
    const ( IDENTIFIER | _ ) : Type ( = Expression )? ;

常量项是一个可选命名的_常量值_,它不与程序中的特定内存位置关联。

常量本质上是在使用它们的地方内联的,这意味着它们在使用时被直接复制到相关上下文中。这包括使用来自外部 crate 的常量和非 Copy 类型。对同一常量的引用不一定保证引用相同的内存地址。

常量声明在其所在的模块或块的值命名空间中定义常量值。

常量必须显式键入。类型必须具有 'static 生命周期:初始化器中的任何引用必须具有 'static 生命周期。常量类型中的引用默认为 'static 生命周期;请参阅静态生命周期省略

如果常量值符合提升条件,则对常量的引用将具有 'static 生命周期;否则,将创建临时值。

#![allow(unused)]
fn main() {
const BIT1: u32 = 1 << 0;
const BIT2: u32 = 1 << 1;

const BITS: [u32; 2] = [BIT1, BIT2];
const STRING: &'static str = "bitstring";

struct BitsNStrings<'a> {
    mybits: [u32; 2],
    mystring: &'a str,
}

const BITS_N_STRINGS: BitsNStrings<'static> = BitsNStrings {
    mybits: BITS,
    mystring: STRING,
};
}

常量表达式只能在 trait 定义中省略。

带有析构器的常量

常量可以包含析构器。当值超出作用域时,析构器会运行。

#![allow(unused)]
fn main() {
struct TypeWithDestructor(i32);

impl Drop for TypeWithDestructor {
    fn drop(&mut self) {
        println!("Dropped. Held {}.", self.0);
    }
}

const ZERO_WITH_DESTRUCTOR: TypeWithDestructor = TypeWithDestructor(0);

fn create_and_drop_zero_with_destructor() {
    let x = ZERO_WITH_DESTRUCTOR;
    // x gets dropped at end of function, calling drop.
    // prints "Dropped. Held 0.".
}
}

未命名常量

关联常量不同,自由常量可以通过使用下划线代替名称来未命名。例如:

#![allow(unused)]
fn main() {
const _: () =  { struct _SameNameTwice; };

// OK although it is the same name as above:
const _: () =  { struct _SameNameTwice; };
}

下划线导入一样,宏可以安全地在同一作用域中多次发出相同的未命名常量。例如,以下不应产生错误:

#![allow(unused)]
fn main() {
macro_rules! m {
    ($item: item) => { $item $item }
}

m!(const _: () = (););
// This expands to:
// const _: () = ();
// const _: () = ();
}

求值

自由常量总是在编译时求值以暴露 panic。即使在未使用的函数中也会发生这种情况:

#![allow(unused)]
fn main() {
// Compile-time panic
const PANIC: () = std::unimplemented!();

fn unused_generic_function<T>() {
    // A failing compile-time assertion
    const _: () = assert!(usize::BITS == 0);
}
}

静态项

Syntax
StaticItem
    ItemSafety?1 static mut? IDENTIFIER : Type ( = Expression )? ;

静态项类似于常量,只是它表示程序中使用初始化表达式初始化的分配。所有对静态的引用和裸指针都引用相同的分配。

静态项具有 static 生命周期,它比 Rust 程序中的所有其他生命周期都长。静态项在程序结束时不调用 drop

如果 static 的大小至少为 1 字节,则此分配与所有其他此类 static 分配以及堆分配和栈分配变量是不相交的。但是,不可变 static 项的存储可以与本身没有唯一地址的分配重叠,例如提升const

静态声明在其所在的模块或块的值命名空间中定义静态值。

静态初始化器是编译时求值的常量表达式。静态初始化器可以引用和读取其他静态。从可变静态读取时,它们读取该静态的初始值。

包含非内部可变类型的非 mut 静态项可以放在只读内存中。

对静态的所有访问都是安全的,但对静态有一些限制:

  • 类型必须具有 Sync trait 约束以允许线程安全访问。

外部块中必须省略初始化表达式,并且必须为自由静态项提供。

safeunsafe 限定符在语义上仅允许在外部块中使用。

静态与泛型

在泛型作用域中定义的静态项(例如在全面实现或默认实现中)将导致恰好定义一个静态项,就像静态定义从当前作用域拉入模块一样。不会每个单态化有一个项。

此代码:

use std::sync::atomic::{AtomicUsize, Ordering};

trait Tr {
    fn default_impl() {
        static COUNTER: AtomicUsize = AtomicUsize::new(0);
        println!("default_impl: counter was {}", COUNTER.fetch_add(1, Ordering::Relaxed));
    }

    fn blanket_impl();
}

struct Ty1 {}
struct Ty2 {}

impl<T> Tr for T {
    fn blanket_impl() {
        static COUNTER: AtomicUsize = AtomicUsize::new(0);
        println!("blanket_impl: counter was {}", COUNTER.fetch_add(1, Ordering::Relaxed));
    }
}

fn main() {
    <Ty1 as Tr>::default_impl();
    <Ty2 as Tr>::default_impl();
    <Ty1 as Tr>::blanket_impl();
    <Ty2 as Tr>::blanket_impl();
}

打印

default_impl: counter was 0
default_impl: counter was 1
blanket_impl: counter was 0
blanket_impl: counter was 1

可变静态

如果静态项使用 mut 关键字声明,则允许程序修改它。Rust 的目标之一是使并发错误难以遇到,而这显然是竞态条件或其他错误的非常大的来源。

因此,在读取或写入可变静态变量时需要 unsafe 块。应注意确保对可变静态的修改对于在同一进程中运行的其他线程是安全的。

然而,可变静态仍然非常有用。它们可以与 C 库一起使用,也可以在 extern 块中从 C 库绑定。

#![allow(unused)]
fn main() {
fn atomic_add(_: *mut u32, _: u32) -> u32 { 2 }

static mut LEVELS: u32 = 0;

// This violates the idea of no shared state, and this doesn't internally
// protect against races, so this function is `unsafe`
unsafe fn bump_levels_unsafe() -> u32 {
    unsafe {
        let ret = LEVELS;
        LEVELS += 1;
        return ret;
    }
}

// As an alternative to `bump_levels_unsafe`, this function is safe, assuming
// that we have an atomic_add function which returns the old value. This
// function is safe only if no other code accesses the static in a non-atomic
// fashion. If such accesses are possible (such as in `bump_levels_unsafe`),
// then this would need to be `unsafe` to indicate to the caller that they
// must still guard against concurrent access.
fn bump_levels_safe() -> u32 {
    unsafe {
        return atomic_add(&raw mut LEVELS, 1);
    }
}
}

可变静态与普通静态具有相同的限制,只是类型不必实现 Sync trait。

使用静态或常量

是否应该使用常量项还是静态项可能会令人困惑。一般来说,应该优先使用常量而不是静态,除非以下情况之一为真:

  • 存储大量数据。
  • 需要静态的单地址属性。
  • 需要内部可变性。

  1. safeunsafe 函数限定符在语义上仅允许在 extern 块内使用。

Trait

Syntax
Trait
    unsafe? trait IDENTIFIER GenericParams? ( : Bounds? )? WhereClause?
    {
        InnerAttribute*
        AssociatedItem*
    }

trait 描述了类型可以实现的抽象接口。此接口由关联项组成,有三种类型:

trait 声明在其所在的模块或块的类型命名空间中定义 trait。

关联项在其各自的命名空间中定义为 trait 的成员。关联类型在类型命名空间中定义。关联常量和关联函数在值命名空间中定义。

所有 trait 都定义了一个隐式类型参数 Self,它指的是“正在实现此接口的类型“。Trait 也可以包含额外的类型参数。这些类型参数(包括 Self)可以像往常一样被其他 trait 约束

Trait 通过单独的实现为特定类型实现。

Trait 函数可以通过用分号替换函数主体来省略函数主体。这表示实现必须定义该函数。如果 trait 函数定义了主体,则此定义充当任何未覆盖它的实现的默认值。类似地,关联常量可以省略等号和表达式以表示实现必须定义常量值。关联类型必须永远不定义类型,类型只能在实现中指定。

#![allow(unused)]
fn main() {
// Examples of associated trait items with and without definitions.
trait Example {
    const CONST_NO_DEFAULT: i32;
    const CONST_WITH_DEFAULT: i32 = 99;
    type TypeNoDefault;
    fn method_without_default(&self);
    fn method_with_default(&self) {}
}
}

Trait 函数不允许是 const

Trait 约束

泛型项可以使用 trait 作为其类型参数的约束

泛型 trait

可以为 trait 指定类型参数以使其成为泛型。这些出现在 trait 名称之后,使用与泛型函数中使用的相同语法。

#![allow(unused)]
fn main() {
trait Seq<T> {
    fn len(&self) -> u32;
    fn elt_at(&self, n: u32) -> T;
    fn iter<F>(&self, f: F) where F: Fn(T);
}
}

Dyn 兼容性

dyn 兼容的 trait 可以是 trait 对象的基础 trait。如果 trait 具有以下品质,则它是dyn 兼容的

  • Sized 不能是超级 trait。换句话说,它不能要求 Self: Sized
  • 它不能有任何关联常量。
  • 它不能有任何带泛型的关联类型。
  • 所有关联函数必须可以从 trait 对象分派,或者显式不可分派:
    • 可分派函数必须:
      • 没有任何类型参数(允许生命周期参数)。
      • 是不使用 Self方法,接收者类型除外。
      • 具有以下类型之一的接收者:
      • 没有不透明返回类型;即,
        • 不是 async fn(具有隐藏的 Future 类型)。
        • 没有返回位置 impl Trait 类型(fn example(&self) -> impl Trait)。
      • 没有 where Self: Sized 约束(Self 类型的接收者(即 self)暗示了这一点)。
    • 显式不可分派函数要求:
      • 具有 where Self: Sized 约束(Self 类型的接收者(即 self)暗示了这一点)。

Note

此概念以前称为对象安全性

#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
// Examples of dyn compatible methods.
trait TraitMethods {
    fn by_ref(self: &Self) {}
    fn by_ref_mut(self: &mut Self) {}
    fn by_box(self: Box<Self>) {}
    fn by_rc(self: Rc<Self>) {}
    fn by_arc(self: Arc<Self>) {}
    fn by_pin(self: Pin<&Self>) {}
    fn with_lifetime<'a>(self: &'a Self) {}
    fn nested_pin(self: Pin<Arc<Self>>) {}
}
struct S;
impl TraitMethods for S {}
let t: Box<dyn TraitMethods> = Box::new(S);
}
#![allow(unused)]
fn main() {
// This trait is dyn compatible, but these methods cannot be dispatched on a trait object.
trait NonDispatchable {
    // Non-methods cannot be dispatched.
    fn foo() where Self: Sized {}
    // Self type isn't known until runtime.
    fn returns(&self) -> Self where Self: Sized;
    // `other` may be a different concrete type of the receiver.
    fn param(&self, other: Self) where Self: Sized {}
    // Generics are not compatible with vtables.
    fn typed<T>(&self, x: T) where Self: Sized {}
}

struct S;
impl NonDispatchable for S {
    fn returns(&self) -> Self where Self: Sized { S }
}
let obj: Box<dyn NonDispatchable> = Box::new(S);
obj.returns(); // ERROR: cannot call with Self return
obj.param(S);  // ERROR: cannot call with Self parameter
obj.typed(1);  // ERROR: cannot call with generic type
}
#![allow(unused)]
fn main() {
use std::rc::Rc;
// Examples of dyn-incompatible traits.
trait DynIncompatible {
    const CONST: i32 = 1;  // ERROR: cannot have associated const

    fn foo() {}  // ERROR: associated function without Sized
    fn returns(&self) -> Self; // ERROR: Self in return type
    fn typed<T>(&self, x: T) {} // ERROR: has generic type parameters
    fn nested(self: Rc<Box<Self>>) {} // ERROR: nested receiver cannot be dispatched on
}

struct S;
impl DynIncompatible for S {
    fn returns(&self) -> Self { S }
}
let obj: Box<dyn DynIncompatible> = Box::new(S); // ERROR
}
#![allow(unused)]
fn main() {
// `Self: Sized` traits are dyn-incompatible.
trait TraitWithSize where Self: Sized {}

struct S;
impl TraitWithSize for S {}
let obj: Box<dyn TraitWithSize> = Box::new(S); // ERROR
}
#![allow(unused)]
fn main() {
// Dyn-incompatible if `Self` is a type argument.
trait Super<A> {}
trait WithSelf: Super<Self> where Self: Sized {}

struct S;
impl<A> Super<A> for S {}
impl WithSelf for S {}
let obj: Box<dyn WithSelf> = Box::new(S); // ERROR: cannot use `Self` type parameter
}

超级 trait

超级 trait 是要求为类型实现特定 trait 的 trait。此外,无论泛型trait 对象在哪里被 trait 约束,它都可以访问其超级 trait 的关联项。

超级 trait 通过 trait 的 Self 类型上的 trait 约束声明,以及那些 trait 约束中声明的 trait 的超级 trait 传递性地声明。trait 成为自己的超级 trait 是错误的。

具有超级 trait 的 trait 称为其超级 trait 的子 trait

以下是将 Shape 声明为 Circle 的超级 trait 的示例。

#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
}

以下是相同的示例,只是使用了 where 子句

#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle where Self: Shape { fn radius(&self) -> f64; }
}

下一个示例使用 Shape 中的 area 函数为 radius 提供默认实现。

#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle where Self: Shape {
    fn radius(&self) -> f64 {
        // A = pi * r^2
        // so algebraically,
        // r = sqrt(A / pi)
        (self.area() / std::f64::consts::PI).sqrt()
    }
}
}

下一个示例在泛型参数上调用超级 trait 方法。

#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
fn print_area_and_radius<C: Circle>(c: C) {
    // Here we call the area method from the supertrait `Shape` of `Circle`.
    println!("Area: {}", c.area());
    println!("Radius: {}", c.radius());
}
}

类似地,这是在 trait 对象上调用超级 trait 方法的示例。

#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
struct UnitCircle;
impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } }
impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } }
let circle = UnitCircle;
let circle = Box::new(circle) as Box<dyn Circle>;
let nonsense = circle.radius() * circle.area();
}

不安全的 trait

unsafe 关键字开头的 trait 项表示实现该 trait 可能是不安全的。使用正确实现的不安全 trait 是安全的。Trait 实现也必须以 unsafe 关键字开头。

SyncSend 是不安全 trait 的示例。

参数模式

没有主体的关联函数中的参数只允许 IDENTIFIER_ 通配符模式,以及 SelfParam 允许的形式。mut IDENTIFIER 目前允许,但已弃用,将来会成为硬错误。

#![allow(unused)]
fn main() {
trait T {
    fn f1(&self);
    fn f2(x: Self, _: i32);
}
}
#![allow(unused)]
fn main() {
trait T {
    fn f2(&x: &i32); // ERROR: patterns aren't allowed in functions without bodies
}
}

有主体的关联函数中的参数只允许不可反驳的模式。

#![allow(unused)]
fn main() {
trait T {
    fn f1((a, b): (i32, i32)) {} // OK: is irrefutable
}
}
#![allow(unused)]
fn main() {
trait T {
    fn f1(123: i32) {} // ERROR: pattern is refutable
    fn f2(Some(x): Option<i32>) {} // ERROR: pattern is refutable
}
}

2018 Edition differences

在 2018 版本之前,关联函数参数的模式是可选的:

#![allow(unused)]
fn main() {
// 2015 Edition
trait T {
    fn f(i32); // OK: parameter identifiers are not required
}
}

从 2018 版本开始,模式不再是可选的。

2018 Edition differences

在 2018 版本之前,有主体的关联函数中的参数仅限于以下类型的模式:

#![allow(unused)]
fn main() {
// 2015 Edition
trait T {
    fn f1((a, b): (i32, i32)) {} // ERROR: pattern not allowed
}
}

从 2018 版本开始,所有不可反驳的模式都如 items.traits.params.patterns-with-body 中所述被允许。

项可见性

Trait 项在语法上允许使用 Visibility 标注,但在验证 trait 时会被拒绝。这允许在使用它们的不同上下文中使用统一的语法解析项。例如,空的 vis 宏片段说明符可用于 trait 项,其中宏规则可能在允许可见性的其他情况下使用。

macro_rules! create_method {
    ($vis:vis $name:ident) => {
        $vis fn $name(&self) {}
    };
}

trait T1 {
    // Empty `vis` is allowed.
    create_method! { method_of_t1 }
}

struct S;

impl S {
    // Visibility is allowed here.
    create_method! { pub method_of_s }
}

impl T1 for S {}

fn main() {
    let s = S;
    s.method_of_t1();
    s.method_of_s();
}

实现

Syntax
ImplementationInherentImpl | TraitImpl

InherentImpl
    impl GenericParams? Type WhereClause? {
        InnerAttribute*
        AssociatedItem*
    }

TraitImpl
    unsafe? impl GenericParams? !? TypePath for Type
    WhereClause?
    {
        InnerAttribute*
        AssociatedItem*
    }

_实现_是将项与_实现类型_关联的项。实现使用关键字 impl 定义,包含属于正在实现的类型的实例或静态地属于该类型的函数。

有两种类型的实现:

  • 固有实现
  • Trait 实现

固有实现

固有实现定义为 impl 关键字、泛型类型声明、到具名类型的路径、where 子句和一组可关联项的序列。

具名类型称为_实现类型_,可关联项是实现类型的_关联项_。

固有实现将包含的项关联到实现类型。

固有实现可以包含关联函数(包括方法)和关联常量

它们不能包含关联类型别名。

到关联项的路径是到实现类型的任何路径,后跟关联项的标识符作为最终路径组件。

一个类型也可以有多个固有实现。实现类型必须在原始类型定义的同一 crate 中定义。

pub mod color {
    pub struct Color(pub u8, pub u8, pub u8);

    impl Color {
        pub const WHITE: Color = Color(255, 255, 255);
    }
}

mod values {
    use super::color::Color;
    impl Color {
        pub fn red() -> Color {
            Color(255, 0, 0)
        }
    }
}

pub use self::color::Color;
fn main() {
    // Actual path to the implementing type and impl in the same module.
    color::Color::WHITE;

    // Impl blocks in different modules are still accessed through a path to the type.
    color::Color::red();

    // Re-exported paths to the implementing type also work.
    Color::red();

    // Does not work, because use in `values` is not pub.
    // values::Color::red();
}

Trait 实现

_trait 实现_的定义类似于固有实现,只是可选的泛型类型声明后跟一个 trait,后跟关键字 for,后跟到具名类型的路径。

该 trait 称为_已实现的 trait_。实现类型实现已实现的 trait。

Trait 实现必须定义已实现 trait 声明的所有非默认关联项,可以重新定义已实现 trait 定义的默认关联项,并且不能定义任何其他项。

到关联项的路径是 < 后跟到实现类型的路径,后跟 as,后跟到 trait 的路径,后跟 > 作为路径组件,后跟关联项的路径组件。

不安全的 trait 要求 trait 实现以 unsafe 关键字开头。

#![allow(unused)]
fn main() {
#[derive(Copy, Clone)]
struct Point {x: f64, y: f64};
type Surface = i32;
struct BoundingBox {x: f64, y: f64, width: f64, height: f64};
trait Shape { fn draw(&self, s: Surface); fn bounding_box(&self) -> BoundingBox; }
fn do_draw_circle(s: Surface, c: Circle) { }
struct Circle {
    radius: f64,
    center: Point,
}

impl Copy for Circle {}

impl Clone for Circle {
    fn clone(&self) -> Circle { *self }
}

impl Shape for Circle {
    fn draw(&self, s: Surface) { do_draw_circle(s, *self); }
    fn bounding_box(&self) -> BoundingBox {
        let r = self.radius;
        BoundingBox {
            x: self.center.x - r,
            y: self.center.y - r,
            width: 2.0 * r,
            height: 2.0 * r,
        }
    }
}
}

Trait 实现一致性

如果孤立规则检查失败或存在重叠的实现实例,则 trait 实现被认为是不一致的。

当实现所针对的 trait 存在非空交集,并且实现可以用相同的类型实例化时,两个 trait 实现重叠。

孤立规则

孤立规则规定,只有当 trait 或实现中的至少一个类型在当前 crate 中定义时,才允许 trait 实现。它防止了跨不同 crate 的冲突 trait 实现,并且是确保一致性的关键。

孤立实现是为外部类型实现外部 trait 的实现。如果自由允许这些,两个 crate 可能以不兼容的方式为同一类型实现相同的 trait,从而导致添加或更新依赖项可能由于冲突的实现而破坏编译的情况。

孤立规则使库作者能够向其 trait 添加新实现,而不必担心它们会破坏下游代码。如果没有这些限制,库就无法添加像 impl<T: Display> MyTrait for T 这样的实现,而不会与下游实现发生潜在冲突。

给定 impl<P1..=Pn> Trait<T1..=Tn> for T0impl 仅在以下至少一项为真时有效:

  • Trait本地 trait
  • 所有以下条件
    • 类型 T0..=Tn 中至少有一个必须是本地类型。让 Ti 是第一个这样的类型。
    • 没有未覆盖类型参数 P1..=Pn 可能出现在 T0..Ti 中(不包括 Ti

只有未覆盖类型参数的出现受到限制。

请注意,出于一致性目的,基础类型 是特殊的。Box<T> 中的 T 不被视为被覆盖,Box<LocalType> 被视为本地。

泛型实现

实现可以接受泛型参数,这些参数可以在实现的其余部分中使用。实现参数直接写在 impl 关键字之后。

#![allow(unused)]
fn main() {
trait Seq<T> { fn dummy(&self, _: T) { } }
impl<T> Seq<T> for Vec<T> {
    /* ... */
}
impl Seq<bool> for u32 {
    /* Treat the integer as a sequence of bits */
}
}

如果参数至少出现在以下位置之一,则泛型参数约束实现:

  • 已实现的 trait(如果有的话)
  • 实现类型
  • 作为包含另一个约束实现的参数的类型的关联类型(在约束中)

类型和 const 参数必须始终约束实现。如果生命周期在关联类型中使用,则生命周期必须约束实现。

约束情况的示例:

#![allow(unused)]
fn main() {
trait Trait{}
trait GenericTrait<T> {}
trait HasAssocType { type Ty; }
struct Struct;
struct GenericStruct<T>(T);
struct ConstGenericStruct<const N: usize>([(); N]);
// T constrains by being an argument to GenericTrait.
impl<T> GenericTrait<T> for i32 { /* ... */ }

// T constrains by being an argument to GenericStruct
impl<T> Trait for GenericStruct<T> { /* ... */ }

// Likewise, N constrains by being an argument to ConstGenericStruct
impl<const N: usize> Trait for ConstGenericStruct<N> { /* ... */ }

// T constrains by being in an associated type in a bound for type `U` which is
// itself a generic parameter constraining the trait.
impl<T, U> GenericTrait<U> for u32 where U: HasAssocType<Ty = T> { /* ... */ }

// Like previous, except the type is `(U, isize)`. `U` appears inside the type
// that includes `T`, and is not the type itself.
impl<T, U> GenericStruct<U> where (U, isize): HasAssocType<Ty = T> { /* ... */ }
}

非约束情况的示例:

#![allow(unused)]
fn main() {
// The rest of these are errors, since they have type or const parameters that
// do not constrain.

// T does not constrain since it does not appear at all.
impl<T> Struct { /* ... */ }

// N does not constrain for the same reason.
impl<const N: usize> Struct { /* ... */ }

// Usage of T inside the implementation does not constrain the impl.
impl<T> Struct {
    fn uses_t(t: &T) { /* ... */ }
}

// T is used as an associated type in the bounds for U, but U does not constrain.
impl<T, U> Struct where U: HasAssocType<Ty = T> { /* ... */ }

// T is used in the bounds, but not as an associated type, so it does not constrain.
impl<T, U> GenericTrait<U> for u32 where U: GenericTrait<T> {}
}

允许的非约束生命周期参数的示例:

#![allow(unused)]
fn main() {
struct Struct;
impl<'a> Struct {}
}

不允许的非约束生命周期参数的示例:

#![allow(unused)]
fn main() {
struct Struct;
trait HasAssocType { type Ty; }
impl<'a> HasAssocType for Struct {
    type Ty = &'a Struct;
}
}

实现上的属性

实现可以在 impl 关键字之前包含外部属性,在包含关联项的括号内包含内部属性。内部属性必须在任何关联项之前。在此处有意义的属性是 cfgdeprecateddoclint 检查属性

外部块

Syntax
ExternBlock
    unsafe?1 extern Abi? {
        InnerAttribute*
        ExternalItem*
    }

ExternalItem
    OuterAttribute* (
        MacroInvocationSemi
      | Visibility? StaticItem
      | Visibility? Function
    )

外部块提供未在当前 crate 中定义的项的_声明_,是 Rust 外部函数接口的基础。这些类似于未检查的导入。

外部块中允许两种类型的项_声明_:函数静态

调用在外部块中声明的不安全函数或访问不安全静态仅在 unsafe 上下文中允许。

外部块在其所在的模块或块的值命名空间中定义其函数和静态。

unsafe 关键字在语义上必须出现在外部块的 extern 关键字之前。

2024 Edition differences

在 2024 版本之前,unsafe 关键字是可选的。safeunsafe 项限定符仅在外部块本身标记为 unsafe 时才允许。

函数

外部块中的函数以与其他 Rust 函数相同的方式声明,不同之处在于它们不能有主体,而是以分号终止。

参数中不允许使用模式,只能使用 IDENTIFIER_

允许使用 safeunsafe 函数限定符,但不允许使用其他函数限定符(例如 constasyncextern)。

外部块中的函数可以像在 Rust 中定义的函数一样被 Rust 代码调用。Rust 编译器自动在 Rust ABI 和外部 ABI 之间进行转换。

在 extern 块中声明的函数除非存在 safe 函数限定符,否则是隐式 unsafe 的。

当强制转换为函数指针时,在 extern 块中声明的函数类型为 for<'l1, ..., 'lm> extern "abi" fn(A1, ..., An) -> R,其中 'l1、… 'lm 是其生命周期参数,A1、…、An 是其参数的声明类型,R 是声明的返回类型。

静态

外部块中的静态以与外部块外部的静态相同的方式声明,不同之处在于它们没有初始化其值的表达式。

除非 extern 块中声明的静态项被限定为 safe,否则访问该项是 unsafe 的,无论它是否可变,因为没有任何东西保证静态内存中的位模式对其声明的类型有效,因为某些任意(例如 C)代码负责初始化静态。

外部静态可以像外部块外部的静态一样是不可变或可变的。

不可变静态必须在执行任何 Rust 代码之前初始化。在 Rust 代码读取之前初始化静态是不够的。一旦 Rust 代码运行,修改不可变静态(从 Rust 内部或外部)是 UB,除非修改发生在 UnsafeCell 内部的字节上。

ABI

extern 关键字后面可以跟一个可选的 ABI 字符串。ABI 指定块中函数的调用约定。调用约定定义了函数的低级接口,例如参数如何放置在寄存器或栈上,如何传递返回值,以及谁负责清理栈。

Example

#![allow(unused)]
fn main() {
// Interface to the Windows API.
unsafe extern "system" { /* ... */ }
}

如果未指定 ABI 字符串,则默认为 "C"

Note

没有显式 ABI 的 extern 语法正在逐步淘汰,因此最好始终显式编写 ABI。

有关更多详细信息,请参阅 Rust issue #134986

所有平台都支持以下 ABI 字符串:

  • unsafe extern "Rust" — Rust 函数和闭包的本机调用约定。当函数声明未使用 extern fn 时,这是默认值。Rust ABI 不提供稳定性保证。
  • unsafe extern "C" — “C” ABI 匹配目标的主流 C 编译器选择的默认 ABI。
  • unsafe extern "system" — 这等同于 extern "C",但在 Windows x86_32 上,对于非可变参数函数等同于 "stdcall",对于可变参数函数等同于 "C"

    Note

    由于 Windows 上正确的底层 ABI 是特定于目标的,因此在尝试链接不使用显式定义的 ABI 的 Windows API 函数时,最好使用 extern "system"

  • extern "C-unwind"extern "system-unwind" — 分别与 "C""system" 相同,但当被调用者展开(通过 panic 或抛出 C++ 风格的异常)时具有不同的行为

还有一些特定于平台的 ABI 字符串:

  • unsafe extern "aapcs" — ARM 的软浮点 ABI。

    • 仅在 ARM32 目标上可用。
    • “aapcs” 与软浮点 ARM32 上的 “C” ABI 相同。
    • 对应于 clang 的 __attribute__((pcs("aapcs")))

    Note

    有关详细信息,请参阅:

  • unsafe extern "efiapi" — 用于 UEFI 函数的 ABI。
    • 仅在 x86 和 ARM 目标(32 位和 64 位)上可用。

"C""system" 一样,大多数特定于平台的 ABI 字符串也有对应的 -unwind 变体;具体来说,这些是:

  • "aapcs-unwind"
  • "cdecl-unwind"
  • "fastcall-unwind"
  • "stdcall-unwind"
  • "sysv64-unwind"
  • "thiscall-unwind"
  • "win64-unwind"

可变参数函数

外部块中的函数可以通过指定 ... 作为最后一个参数来成为可变参数。可变参数可以选择使用标识符指定。

#![allow(unused)]
fn main() {
unsafe extern "C" {
    unsafe fn foo(...);
    unsafe fn bar(x: i32, ...);
    unsafe fn with_name(format: *const u8, args: ...);
    // SAFETY: This function guarantees it will not access
    // variadic arguments.
    safe fn ignores_variadic_arguments(x: i32, ...);
}
}

Warning

除非该函数保证完全不会访问可变参数,否则不应在 extern 块中的函数上使用 safe 限定符。向可变参数函数传递意外数量的参数或意外类型的参数可能导致未定义行为

可变参数只能在具有以下 ABI 字符串或其对应的 -unwind 变体extern 块中指定:

  • "aapcs"
  • "C"
  • "cdecl"
  • "efiapi"
  • "system"
  • "sysv64"
  • "win64"

外部块上的属性

以下属性控制外部块的行为。

link 属性指定编译器应为 extern 块中的项链接的本机库的名称。

它使用 MetaListNameValueStr 语法指定其输入。name 键是要链接的本机库的名称。kind 键是一个可选值,指定库的类型,具有以下可能的值:

  • dylib — 表示动态库。如果未指定 kind,则这是默认值。
  • static — 表示静态库。
  • framework — 表示 macOS 框架。这仅对 macOS 目标有效。
  • raw-dylib — 表示编译器将生成导入库以链接的动态库(有关详细信息,请参阅下面的 dylibraw-dylib)。这仅对 Windows 目标有效。

如果指定了 kind,则必须包含 name 键。

可选的 modifiers 参数是一种为要链接的库指定链接修饰符的方式。

修饰符指定为逗号分隔的字符串,每个修饰符前缀为 +-,分别表示启用或禁用修饰符。

当前不支持在单个 link 属性中指定多个 modifiers 参数,或在同一个 modifiers 参数中指定多个相同的修饰符。示例:#[link(name = "mylib", kind = "static", modifiers = "+whole-archive")]

当从主机环境导入符号时,wasm_import_module 键可用于指定 extern 块中项的 WebAssembly 模块名称。如果未指定 wasm_import_module,则默认模块名称为 env

#[link(name = "crypto")]
unsafe extern {
    // …
}

#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern {
    // …
}

#[link(wasm_import_module = "foo")]
unsafe extern {
    // …
}

在空的 extern 块上添加 link 属性是有效的。您可以使用此功能来满足代码中其他地方(包括上游 crate)的 extern 块的链接要求,而不是将属性添加到每个 extern 块。

链接修饰符:bundle

此修饰符仅与 static 链接类型兼容。使用任何其他类型将导致编译器错误。

构建 rlib 或 staticlib 时,+bundle 意味着本机静态库将被打包到 rlib 或 staticlib 归档中,然后在最终二进制文件链接期间从那里检索。

构建 rlib 时,-bundle 意味着本机静态库“按名称“注册为该 rlib 的依赖项,并且其目标文件仅在最终二进制文件链接期间包含,按该名称的文件搜索也在最终链接期间执行。构建 staticlib 时,-bundle 意味着本机静态库根本不包含在归档中,某些更高级别的构建系统将需要稍后在最终二进制文件链接期间添加它。

构建其他目标(如可执行文件或动态库)时,此修饰符无效。

此修饰符的默认值为 +bundle

有关此修饰符的更多实现细节,请参阅 bundle 文档

链接修饰符:whole-archive

此修饰符仅与 static 链接类型兼容。使用任何其他类型将导致编译器错误。

+whole-archive 意味着静态库作为整个归档链接,不丢弃任何目标文件。

此修饰符的默认值为 -whole-archive

有关此修饰符的更多实现细节,请参阅 whole-archive 文档

链接修饰符:verbatim

此修饰符与所有链接类型兼容。

+verbatim 意味着 rustc 本身不会向库名称添加任何目标指定的库前缀或后缀(如 lib.a),并将尽力要求链接器执行相同操作。

-verbatim 意味着 rustc 要么在将库名称传递给链接器之前添加特定于目标的前缀和后缀,要么不会阻止链接器隐式添加它。

此修饰符的默认值为 -verbatim

有关此修饰符的更多实现细节,请参阅 verbatim 文档

dylibraw-dylib

在 Windows 上,链接动态库需要向链接器提供导入库:这是一种特殊的静态库,它声明动态库导出的所有符号,以便链接器知道它们必须在运行时动态加载。

指定 kind = "dylib" 指示 Rust 编译器基于 name 键链接导入库。然后链接器将使用其正常的库解析逻辑来查找该导入库。或者,指定 kind = "raw-dylib" 指示编译器在编译期间生成导入库并将其提供给链接器。

raw-dylib 仅在 Windows 上受支持。在针对其他平台使用它将导致编译器错误。

import_name_type

在 x86 Windows 上,函数名称被“修饰“(即添加特定的前缀和/或后缀)以指示其调用约定。例如,名为 fn1 的无参数 stdcall 调用约定函数将被修饰为 _fn1@0。但是,PE 格式 也允许名称没有前缀或未修饰。此外,MSVC 和 GNU 工具链对相同的调用约定使用不同的修饰,这意味着默认情况下,某些 Win32 函数无法通过 GNU 工具链使用 raw-dylib 链接类型调用。

为了允许这些差异,当使用 raw-dylib 链接类型时,您还可以指定 import_name_type 键,其值为以下之一,以更改生成的导入库中函数的命名方式:

  • decorated:函数名称将使用 MSVC 工具链格式完全修饰。
  • noprefix:函数名称将使用 MSVC 工具链格式修饰,但跳过前导 ?@ 或可选的 _
  • undecorated:函数名称将不被修饰。

如果未指定 import_name_type 键,则函数名称将使用目标工具链的格式完全修饰。

变量从不被修饰,因此 import_name_type 键对它们在生成的导入库中的命名方式没有影响。

import_name_type 键仅在 x86 Windows 上受支持。在针对其他平台使用它将导致编译器错误。

link_name 属性 可应用于 extern 块内的声明,以指定为给定函数或静态导入的符号。

Example

#![allow(unused)]
fn main() {
unsafe extern "C" {
    #[link_name = "actual_symbol_name"]
    safe fn name_in_rust();
}
}

link_name 属性使用 MetaNameValueStr 语法。

符号名称不能为空字符串或包含任何 U+0000 (NUL) 字节。

link_name 属性只能应用于 extern 块中的函数或静态项。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

只有项上的第一次使用 link_name 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告,并带有未来兼容性警告。这可能在未来成为错误。

link_name 属性不能与 link_ordinal 属性一起使用。

link_ordinal 属性可应用于 extern 块内的声明,以指示生成导入库以链接时使用的数字序号。序号是 Windows 上动态库导出的每个符号的唯一编号,可用于在加载库时查找该符号,而不必按名称查找。

Warning

link_ordinal 应仅在已知符号的序号稳定的情况下使用:如果在构建其包含的二进制文件时未显式设置符号的序号,则会自动为其分配一个,并且该分配的序号可能在二进制文件的构建之间发生变化。

#![allow(unused)]
fn main() {
#[cfg(all(windows, target_arch = "x86"))]
#[link(name = "exporter", kind = "raw-dylib")]
unsafe extern "stdcall" {
    #[link_ordinal(15)]
    safe fn imported_function_stdcall(i: i32);
}
}

此属性仅与 raw-dylib 链接类型一起使用。使用任何其他类型将导致编译器错误。

将此属性与 link_name 属性一起使用将导致编译器错误。

函数参数上的属性

extern 函数参数上的属性遵循与常规函数参数相同的规则和限制。


  1. 从 2024 版本开始,unsafe 关键字在语义上是必需的。

泛型参数

Syntax
GenericParams< ( GenericParam ( , GenericParam )* ,? )? >

GenericParamOuterAttribute* ( LifetimeParam | TypeParam | ConstParam )

LifetimeParamLifetime ( : LifetimeBounds )?

TypeParamIDENTIFIER ( : Bounds? )? ( = Type )?

ConstParam
    const IDENTIFIER : Type
    ( = ( BlockExpression | IDENTIFIER | -? LiteralExpression ) )?

函数类型别名结构体枚举联合体trait实现 可以通过类型、常量和生命周期进行参数化。这些参数列在尖括号<...>中,通常紧跟在项的名称之后、其定义之前。对于没有名称的实现,它们直接跟在 impl 之后。

泛型参数的顺序仅限于生命周期参数,然后是类型和 const 参数的混合。

GenericParams 列表中不能多次声明相同的参数名称。

具有类型、const 和生命周期参数的项的一些示例:

#![allow(unused)]
fn main() {
fn foo<'a, T>() {}
trait A<U> {}
struct Ref<'a, T> where T: 'a { r: &'a T }
struct InnerArray<T, const N: usize>([T; N]);
struct EitherOrderWorks<const N: bool, U>(U);
}

泛型参数在声明它们的项定义的作用域内。它们不在项声明中描述的函数主体内声明的项的作用域内。有关更多详细信息,请参阅泛型参数作用域

引用裸指针数组切片元组函数指针 也有生命周期或类型参数,但不使用路径语法引用。

'_'static 不是有效的生命周期参数名称。

Const 泛型

Const 泛型参数允许项对常量值进行泛型。

const 标识符在值命名空间中为常量参数引入一个名称,项的所有实例必须使用给定类型的值实例化。

唯一允许的 const 参数类型是 u8u16u32u64u128usizei8i16i32i64i128isizecharbool

Const 参数可以在常量项可以使用的任何地方使用,但在类型数组重复表达式中使用时,它必须是独立的(如下所述)。也就是说,它们在以下位置被允许:

  1. 作为应用于任何类型的已应用 const,该类型构成相关项签名的一部分。
  2. 作为用于定义关联常量的 const 表达式的一部分,或作为关联类型的参数。
  3. 作为项中任何函数主体中任何运行时表达式的值。
  4. 作为项中任何函数主体中使用的任何类型的参数。
  5. 作为项中任何字段的类型的一部分。
#![allow(unused)]
fn main() {
// Examples where const generic parameters can be used.

// Used in the signature of the item itself.
fn foo<const N: usize>(arr: [i32; N]) {
    // Used as a type within a function body.
    let x: [i32; N];
    // Used as an expression.
    println!("{}", N * 2);
}

// Used as a field of a struct.
struct Foo<const N: usize>([i32; N]);

impl<const N: usize> Foo<N> {
    // Used as an associated constant.
    const CONST: usize = N * 4;
}

trait Trait {
    type Output;
}

impl<const N: usize> Trait for Foo<N> {
    // Used as an associated type.
    type Output = [i32; N];
}
}
#![allow(unused)]
fn main() {
// Examples where const generic parameters cannot be used.
fn foo<const N: usize>() {
    // Cannot use in item definitions within a function body.
    const BAD_CONST: [usize; N] = [1; N];
    static BAD_STATIC: [usize; N] = [1; N];
    fn inner(bad_arg: [usize; N]) {
        let bad_value = N * 2;
    }
    type BadAlias = [usize; N];
    struct BadStruct([usize; N]);
}
}

作为进一步的限制,const 参数只能作为独立参数出现在类型数组重复表达式内部。在这些上下文中,它们只能用作单个段路径表达式,可能在内部(如 N{N})。也就是说,它们不能与其他表达式组合。

#![allow(unused)]
fn main() {
// Examples where const parameters may not be used.

// Not allowed to combine in other expressions in types, such as the
// arithmetic expression in the return type here.
fn bad_function<const N: usize>() -> [u8; {N + 1}] {
    // Similarly not allowed for array repeat expressions.
    [1; {N + 1}]
}
}

路径中的 const 参数指定要用于该项的 const 值。

参数必须是推断 const 或归因于 const 参数的类型的常量表达式。常量表达式必须是[块表达式][block expression](用大括号括起来),除非它是单个路径段(IDENTIFIER)或字面量(可能带有前导 - 词法单元)。

Note

此语法限制是必要的,以避免在解析类型内的表达式时需要无限前瞻。

#![allow(unused)]
fn main() {
struct S<const N: i64>;
const C: i64 = 1;
fn f<const N: i64>() -> S<N> { S }

let _ = f::<1>(); // Literal.
let _ = f::<-1>(); // Negative literal.
let _ = f::<{ 1 + 2 }>(); // Constant expression.
let _ = f::<C>(); // Single segment path.
let _ = f::<{ C + 1 }>(); // Constant expression.
let _: S<1> = f::<_>(); // Inferred const.
let _: S<1> = f::<(((_)))>(); // Inferred const.
}

Note

在泛型参数列表中,推断 const 被解析为推断类型,但在语义上被视为单独的const 泛型参数

在期望 const 参数的地方,可以使用 _(可选地用任意数量的匹配括号括起来),称为推断 const路径规则数组表达式规则)。这要求编译器根据周围信息推断 const 参数(如果可能)。

#![allow(unused)]
fn main() {
fn make_buf<const N: usize>() -> [u8; N] {
    [0; _]
    //  ^ Infers `N`.
}
let _: [u8; 1024] = make_buf::<_>();
//                             ^ Infers `1024`.
}

Note

推断 const 在语义上不是表达式,因此在大括号内不被接受。

#![allow(unused)]
fn main() {
fn f<const N: usize>() -> [u8; N] { [0; _] }
let _: [_; 1] = f::<{ _ }>();
//                    ^ ERROR `_` not allowed here
}

推断 const 不能在项签名中使用。

#![allow(unused)]
fn main() {
fn f<const N: usize>(x: [u8; N]) -> [u8; _] { x }
//                                       ^ ERROR not allowed
}

当存在歧义,即泛型参数可以解析为类型或 const 参数时,它始终被解析为类型。将参数放在块表达式中可以强制将其解释为 const 参数。

#![allow(unused)]
fn main() {
type N = u32;
struct Foo<const N: usize>;
// The following is an error, because `N` is interpreted as the type alias `N`.
fn foo<const N: usize>() -> Foo<N> { todo!() } // ERROR
// Can be fixed by wrapping in braces to force it to be interpreted as the `N`
// const parameter:
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
}

与类型和生命周期参数不同,const 参数可以在参数化项内声明而不使用,但泛型实现中描述的实现除外:

#![allow(unused)]
fn main() {
// ok
struct Foo<const N: usize>;
enum Bar<const M: usize> { A, B }

// ERROR: unused parameter
struct Baz<T>;
struct Biz<'a>;
struct Unconstrained;
impl<const N: usize> Unconstrained {}
}

解析 trait 约束义务时,在确定约束是否满足时不考虑 const 参数的所有实现的穷举性。例如,在以下示例中,即使实现了 bool 类型的所有可能 const 值,trait 约束未满足仍然是错误的:

#![allow(unused)]
fn main() {
struct Foo<const B: bool>;
trait Bar {}
impl Bar for Foo<true> {}
impl Bar for Foo<false> {}

fn needs_bar(_: impl Bar) {}
fn generic<const B: bool>() {
    let v = Foo::<B>;
    needs_bar(v); // ERROR: trait bound `Foo<B>: Bar` is not satisfied
}
}

Where 子句

Where 子句提供了另一种方式来指定类型和生命周期参数的约束,以及一种方式来指定非类型参数的类型上的约束。

for 关键字可用于引入高阶生命周期。它只允许 LifetimeParam 参数。

#![allow(unused)]
fn main() {
struct A<T>
where
    T: Iterator,            // Could use A<T: Iterator> instead
    T::Item: Copy,          // Bound on an associated type
    String: PartialEq<T>,   // Bound on `String`, using the type parameter
    i32: Default,           // Allowed, but not useful
{
    f: T,
}
}

属性

泛型生命周期和类型参数允许在它们上面使用属性。在此位置没有任何内置属性可以做任何事情,尽管自定义派生属性可能会赋予其意义。

此示例显示使用自定义派生属性来修改泛型参数的含义。

// Assume that the derive for MyFlexibleClone declared `my_flexible_clone` as
// an attribute it understands.
#[derive(MyFlexibleClone)]
struct Foo<#[my_flexible_clone(unbounded)] H> {
    a: *const H
}

关联项

Syntax
AssociatedItem
    OuterAttribute* (
        MacroInvocationSemi
      | ( Visibility? ( TypeAlias | ConstantItem | Function ) )
    )

关联项是在 trait 中声明或在实现中定义的项。它们之所以被称为关联项,是因为它们定义在关联类型上 — 即实现中的类型。

它们是您可以在模块中声明的项类型的子集。具体来说,有关联函数(包括方法)、关联类型关联常量

当关联项在逻辑上与关联项相关时,关联项很有用。例如,Option 上的 is_some 方法本质上与 Options 相关,因此应该关联。

每种关联项都有两种变体:包含实际实现的定义和声明签名的声明。

正是声明构成了 trait 的契约以及泛型类型上可用的内容。

关联函数和方法

关联函数是与类型关联的函数

关联函数声明为关联函数定义声明签名。它被编写为函数项,只是函数主体被替换为 ;

标识符是函数的名称。

关联函数的泛型、参数列表、返回类型和 where 子句必须与关联函数声明的相同。

关联函数定义定义与另一个类型关联的函数。它的编写方式与函数项相同。

Note

一个常见的示例是名为 new 的关联函数,它返回与其关联的类型的值。

struct Struct {
    field: i32
}

impl Struct {
    fn new() -> Struct {
        Struct {
            field: 0i32
        }
    }
}

fn main () {
    let _struct = Struct::new();
}

当关联函数在 trait 上声明时,也可以使用作为到 trait 的路径后跟 trait 名称的路径来调用该函数。当发生这种情况时,它被替换为 <_ as Trait>::function_name

#![allow(unused)]
fn main() {
trait Num {
    fn from_i32(n: i32) -> Self;
}

impl Num for f64 {
    fn from_i32(n: i32) -> f64 { n as f64 }
}

// These 4 are all equivalent in this case.
let _: f64 = Num::from_i32(42);
let _: f64 = <_ as Num>::from_i32(42);
let _: f64 = <f64 as Num>::from_i32(42);
let _: f64 = f64::from_i32(42);
}

方法

第一个参数名为 self 的关联函数称为方法,可以使用方法调用运算符调用,例如 x.foo(),以及通常的函数调用表示法。

如果指定了 self 参数的类型,则它仅限于解析为以下语法生成的类型(其中 'lt 表示某个任意生命周期):

P = &'lt S | &'lt mut S | Box<S> | Rc<S> | Arc<S> | Pin<P>
S = Self | P

此语法中的 Self 终端表示解析为实现类型的类型。这也可以包括上下文类型别名 Self、其他类型别名或解析为实现类型的关联类型投影。

#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
// Examples of methods implemented on struct `Example`.
struct Example;
type Alias = Example;
trait Trait { type Output; }
impl Trait for Example { type Output = Example; }
impl Example {
    fn by_value(self: Self) {}
    fn by_ref(self: &Self) {}
    fn by_ref_mut(self: &mut Self) {}
    fn by_box(self: Box<Self>) {}
    fn by_rc(self: Rc<Self>) {}
    fn by_arc(self: Arc<Self>) {}
    fn by_pin(self: Pin<&Self>) {}
    fn explicit_type(self: Arc<Example>) {}
    fn with_lifetime<'a>(self: &'a Self) {}
    fn nested<'a>(self: &mut &'a Arc<Rc<Box<Alias>>>) {}
    fn via_projection(self: <Example as Trait>::Output) {}
}
}

可以使用简写语法而不指定类型,它们具有以下等效形式:

简写等效形式
selfself: Self
&'lifetime selfself: &'lifetime Self
&'lifetime mut selfself: &'lifetime mut Self

Note

使用此简写时,生命周期可以并且通常会被省略。

如果 self 参数以 mut 为前缀,则它成为可变变量,类似于使用 mut 标识符模式的常规参数。例如:

#![allow(unused)]
fn main() {
trait Changer: Sized {
    fn change(mut self) {}
    fn modify(mut self: Box<Self>) {}
}
}

作为 trait 上方法的示例,请考虑以下内容:

#![allow(unused)]
fn main() {
type Surface = i32;
type BoundingBox = i32;
trait Shape {
    fn draw(&self, surface: Surface);
    fn bounding_box(&self) -> BoundingBox;
}
}

这定义了一个具有两个方法的 trait。所有具有此 trait 的实现的值(当 trait 在作用域中时)都可以调用其 drawbounding_box 方法。

#![allow(unused)]
fn main() {
type Surface = i32;
type BoundingBox = i32;
trait Shape {
    fn draw(&self, surface: Surface);
    fn bounding_box(&self) -> BoundingBox;
}

struct Circle {
    // ...
}

impl Shape for Circle {
    // ...
  fn draw(&self, _: Surface) {}
  fn bounding_box(&self) -> BoundingBox { 0i32 }
}

impl Circle {
    fn new() -> Circle { Circle{} }
}

let circle_shape = Circle::new();
let bounding_box = circle_shape.bounding_box();
}

2018 Edition differences

在 2015 版本中,可以使用匿名参数声明 trait 方法(例如 fn foo(u8))。这已弃用,从 2018 版本开始成为错误。所有参数必须有参数名称。

方法参数上的属性

方法参数上的属性遵循与常规函数参数相同的规则和限制。

关联类型

关联类型是与另一个类型关联的类型别名

关联类型不能在固有实现中定义,也不能在 trait 中给出默认实现。

关联类型声明为关联类型定义声明签名。它以以下形式之一编写,其中 Assoc 是关联类型的名称,Params 是类型、生命周期或 const 参数的逗号分隔列表,Bounds 是关联类型必须满足的 trait 约束的加号分隔列表,WhereBounds 是参数必须满足的约束的逗号分隔列表:

type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;

标识符是声明的类型别名的名称。

可选的 trait 约束必须由类型别名的实现满足。

关联类型上有一个隐式的 Sized 约束,可以使用特殊的 ?Sized 约束来放宽。

关联类型定义为 trait 在类型上的实现定义类型别名。

它们的编写方式类似于关联类型声明,但不能包含 Bounds,而必须包含 Type

type Assoc = Type;
type Assoc<Params> = Type; // the type `Type` here may reference `Params`
type Assoc<Params> = Type where WhereBounds;
type Assoc<Params> where WhereBounds = Type; // deprecated, prefer the form above

如果类型 Item 从 trait Trait 具有关联类型 Assoc,则 <Item as Trait>::Assoc 是关联类型定义中指定类型的别名。

此外,如果 Item 是类型参数,则 Item::Assoc 可以在类型参数中使用。

关联类型可以包含泛型参数where 子句;这些通常称为泛型关联类型GAT。如果类型 Thing 从具有泛型 <'a> 的 trait Trait 具有关联类型 Item,则该类型可以命名为 <Thing as Trait>::Item<'x>,其中 'x 是作用域中的某个生命周期。在这种情况下,'x 将在 impl 上的关联类型定义中 'a 出现的任何地方使用。

trait AssociatedType {
    // Associated type declaration
    type Assoc;
}

struct Struct;

struct OtherStruct;

impl AssociatedType for Struct {
    // Associated type definition
    type Assoc = OtherStruct;
}

impl OtherStruct {
    fn new() -> OtherStruct {
        OtherStruct
    }
}

fn main() {
    // Usage of the associated type to refer to OtherStruct as <Struct as AssociatedType>::Assoc
    let _other_struct: OtherStruct = <Struct as AssociatedType>::Assoc::new();
}

具有泛型和 where 子句的关联类型的示例:

struct ArrayLender<'a, T>(&'a mut [T; 16]);

trait Lend {
    // Generic associated type declaration
    type Lender<'a> where Self: 'a;
    fn lend<'a>(&'a mut self) -> Self::Lender<'a>;
}

impl<T> Lend for [T; 16] {
    // Generic associated type definition
    type Lender<'a> = ArrayLender<'a, T> where Self: 'a;

    fn lend<'a>(&'a mut self) -> Self::Lender<'a> {
        ArrayLender(self)
    }
}

fn borrow<'a, T: Lend>(array: &'a mut T) -> <T as Lend>::Lender<'a> {
    array.lend()
}

fn main() {
    let mut array = [0usize; 16];
    let lender = borrow(&mut array);
}

关联类型容器示例

考虑以下 Container trait 的示例。请注意,该类型可在方法签名中使用:

#![allow(unused)]
fn main() {
trait Container {
    type E;
    fn empty() -> Self;
    fn insert(&mut self, elem: Self::E);
}
}

为了使类型实现此 trait,它不仅必须为每个方法提供实现,还必须指定类型 E。这是标准库类型 VecContainer 实现:

#![allow(unused)]
fn main() {
trait Container {
    type E;
    fn empty() -> Self;
    fn insert(&mut self, elem: Self::E);
}
impl<T> Container for Vec<T> {
    type E = T;
    fn empty() -> Vec<T> { Vec::new() }
    fn insert(&mut self, x: T) { self.push(x); }
}
}

BoundsWhereBounds 之间的关系

在此示例中:

#![allow(unused)]
fn main() {
use std::fmt::Debug;
trait Example {
    type Output<T>: Ord where T: Debug;
}
}

给定对关联类型的引用如 <X as Example>::Output<Y>,关联类型本身必须是 Ord,类型 Y 必须是 Debug

泛型关联类型上的必需 where 子句

trait 上的泛型关联类型声明目前可能需要 where 子句列表,具体取决于 trait 中的函数以及 GAT 的使用方式。这些规则将来可能会放宽;更新可以在泛型关联类型倡议仓库上找到。

简而言之,需要这些 where 子句是为了最大化 impl 中关联类型的允许定义。为此,任何可以证明成立的关于函数(使用函数或 trait 的参数)的子句,其中 GAT 作为输入或输出出现,也必须写在 GAT 本身上。

#![allow(unused)]
fn main() {
trait LendingIterator {
    type Item<'x> where Self: 'x;
    fn next<'a>(&'a mut self) -> Self::Item<'a>;
}
}

在上面的 next 函数中,我们可以证明 Self: 'a,因为来自 &'a mut self 的隐式约束;因此,我们必须在 GAT 本身上编写等效的约束:where Self: 'x

当 trait 中有多个函数使用 GAT 时,使用来自不同函数的约束的交集,而不是并集。

#![allow(unused)]
fn main() {
trait Check<T> {
    type Checker<'x>;
    fn create_checker<'a>(item: &'a T) -> Self::Checker<'a>;
    fn do_check(checker: Self::Checker<'_>);
}
}

在此示例中,type Checker<'a>; 不需要任何约束。虽然我们在 create_checker 上知道 T: 'a,但在 do_check 上不知道。但是,如果 do_check 被注释掉,则 Checker 上需要 where T: 'x 约束。

关联类型上的约束也会传播必需的 where 子句。

#![allow(unused)]
fn main() {
trait Iterable {
    type Item<'a> where Self: 'a;
    type Iterator<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a;
    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}

这里,Item 上需要 where Self: 'a 是因为 iter。然而,Item 用于 Iterator 的约束中,where Self: 'a 子句在那里也需要。

最后,trait 上 GAT 的任何显式 'static 使用不计入必需的约束。

#![allow(unused)]
fn main() {
trait StaticReturn {
    type Y<'a>;
    fn foo(&self) -> Self::Y<'static>;
}
}

关联常量

关联常量是与类型关联的常量

关联常量声明为关联常量定义声明签名。它被编写为 const,然后是标识符,然后是 :,然后是类型,以 ; 结束。

标识符是路径中使用的常量名称。类型是定义必须实现的类型。

关联常量定义定义与类型关联的常量。它的编写方式与常量项相同。

关联常量定义仅在被引用时才进行常量求值。此外,包含泛型参数的定义在单态化后求值。

struct Struct;
struct GenericStruct<const ID: i32>;

impl Struct {
    // Definition not immediately evaluated
    const PANIC: () = panic!("compile-time panic");
}

impl<const ID: i32> GenericStruct<ID> {
    // Definition not immediately evaluated
    const NON_ZERO: () = if ID == 0 {
        panic!("contradiction")
    };
}

fn main() {
    // Referencing Struct::PANIC causes compilation error
    let _ = Struct::PANIC;

    // Fine, ID is not 0
    let _ = GenericStruct::<1>::NON_ZERO;

    // Compilation error from evaluating NON_ZERO with ID=0
    let _ = GenericStruct::<0>::NON_ZERO;
}

关联常量示例

基本示例:

trait ConstantId {
    const ID: i32;
}

struct Struct;

impl ConstantId for Struct {
    const ID: i32 = 1;
}

fn main() {
    assert_eq!(1, Struct::ID);
}

使用默认值:

trait ConstantIdDefault {
    const ID: i32 = 1;
}

struct Struct;
struct OtherStruct;

impl ConstantIdDefault for Struct {}

impl ConstantIdDefault for OtherStruct {
    const ID: i32 = 5;
}

fn main() {
    assert_eq!(1, Struct::ID);
    assert_eq!(5, OtherStruct::ID);
}

属性

Syntax
InnerAttribute# ! [ Attr ]

OuterAttribute# [ Attr ]

Attr
      SimplePath AttrInput?
    | unsafe ( SimplePath AttrInput? )

AttrInput
      DelimTokenTree
    | = Expression

属性(Attribute)是一种通用的、自由形式的元数据,根据名称、约定、语言和编译器版本进行解释。属性基于 ECMA-335 中的属性建模,语法来自 ECMA-334 (C#)。

_内部属性_在哈希(#)后加感叹号(!)编写,应用于属性声明所在的形式。

Example

#![allow(unused)]
fn main() {
// General metadata applied to the enclosing module or crate.
#![crate_type = "lib"]

// Inner attribute applies to the entire function.
fn some_unused_variables() {
  #![allow(unused_variables)]

  let x = ();
  let y = ();
  let z = ();
}
}

_外部属性_在哈希后不加感叹号编写,应用于属性之后的形式。

Example

#![allow(unused)]
fn main() {
// A function marked as a unit test
#[test]
fn test_foo() {
    /* ... */
}

// A conditionally-compiled module
#[cfg(target_os = "linux")]
mod bar {
    /* ... */
}

// A lint attribute used to suppress a warning/error
#[allow(non_camel_case_types)]
type int8_t = i8;
}

属性由到属性的路径组成,后跟可选的分隔词法单元树,其解释由属性定义。除宏属性外,属性还允许输入为等号(=)后跟表达式。有关更多详细信息,请参阅下面的[元项语法][meta item syntax]。

属性可能是不安全的。为了避免使用这些属性时的未定义行为,必须满足编译器无法检查的某些义务。为了断言这些义务已满足,属性被包装在 unsafe(..) 中,例如 #[unsafe(no_mangle)]

以下属性是不安全的:

属性可分为以下几类:

属性可应用于语言中的多种形式:

元项属性语法

“元项“是大多数内置属性用于 Attr 规则的语法。它具有以下语法:

Syntax
MetaItem
      SimplePath
    | SimplePath = Expression
    | SimplePath ( MetaSeq? )

MetaSeq
    MetaItemInner ( , MetaItemInner )* ,?

MetaItemInner
      MetaItem
    | Expression

元项中的表达式必须宏展开为字面量表达式,不得包含整数或浮点类型后缀。不是字面量表达式的表达式将在语法上被接受(并且可以传递给过程宏),但在解析后将被拒绝。

请注意,如果属性出现在另一个宏内,它将在该外部宏之后展开。例如,以下代码将首先展开 Serialize 过程宏,该宏必须保留 include_str! 调用以便它可以被展开:

#[derive(Serialize)]
struct Foo {
    #[doc = include_str!("x.md")]
    x: u32
}

此外,属性中的宏只会在应用于该项的所有其他属性之后展开:

#[macro_attr1] // expanded first
#[doc = mac!()] // `mac!` is expanded fourth.
#[macro_attr2] // expanded second
#[derive(MacroDerive1, MacroDerive2)] // expanded third
fn foo() {}

各种内置属性使用元项语法的不同子集来指定其输入。以下语法规则显示了一些常用形式:

Syntax
MetaWord
    IDENTIFIER

MetaNameValueStr
    IDENTIFIER = ( STRING_LITERAL | RAW_STRING_LITERAL )

MetaListPaths
    IDENTIFIER ( ( SimplePath ( , SimplePath )* ,? )? )

MetaListIdents
    IDENTIFIER ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )

MetaListNameValueStr
    IDENTIFIER ( ( MetaNameValueStr ( , MetaNameValueStr )* ,? )? )

元项的一些示例:

样式示例
MetaWordno_std
MetaNameValueStrdoc = "example"
MetaListPathsallow(unused, clippy::inline_always)
MetaListIdentsmacro_use(foo, bar)
MetaListNameValueStrlink(name = "CoreFoundation", kind = "framework")

活动和惰性属性

属性要么是活动的,要么是惰性的。在属性处理期间,活动属性从它们所在的形式中移除,而惰性属性保留。

cfgcfg_attr 属性是活动的。属性宏是活动的。所有其他属性都是惰性的。

工具属性

编译器可能允许外部工具的属性,每个工具位于工具 prelude中的自己的模块中。属性路径的第一段是工具的名称,有一个或多个额外段,其解释由工具决定。

当工具未使用时,工具的属性被接受而不发出警告。当工具正在使用时,工具负责处理和解释其属性。

如果使用了 no_implicit_prelude 属性,则工具属性不可用。

#![allow(unused)]
fn main() {
// Tells the rustfmt tool to not format the following element.
#[rustfmt::skip]
struct S {
}

// Controls the "cyclomatic complexity" threshold for the clippy tool.
#[clippy::cyclomatic_complexity = "100"]
pub fn f() {}
}

Note

rustc 目前识别工具 “clippy”、“rustfmt”、“diagnostic”、“miri” 和 “rust_analyzer”。

内置属性索引

以下是所有内置属性的索引。

  • 条件编译

    • cfg — 控制条件编译。
    • cfg_attr — 有条件地包含属性。
  • 测试

    • test — 将函数标记为测试。
    • ignore — 禁用测试函数。
    • should_panic — 指示测试应生成 panic。
  • 派生

  • 诊断

  • ABI、链接、符号和 FFI

    • link — 指定要与 extern 块链接的本机库。
    • link_name — 指定 extern 块中函数或静态的符号名称。
    • link_ordinal — 指定 extern 块中函数或静态的符号序号。
    • no_link — 阻止链接外部 crate。
    • repr — 控制类型布局。
    • crate_type — 指定 crate 类型(库、可执行文件等)。
    • no_main — 禁用发出 main 符号。
    • export_name — 指定函数或静态的导出符号名称。
    • link_section — 指定用于函数或静态的目标文件部分。
    • no_mangle — 禁用符号名称编码。
    • used — 强制编译器在输出目标文件中保留静态项。
    • crate_name — 指定 crate 名称。
  • 代码生成

    • inline — 内联代码提示。
    • cold — 函数不太可能被调用的提示。
    • naked — 阻止编译器发出函数序言和尾声。
    • no_builtins — 禁用某些内置函数的使用。
    • target_feature — 配置特定于平台的代码生成。
    • track_caller — 将父调用位置传递给 std::panic::Location::caller()
    • instruction_set — 指定用于生成函数代码的指令集。
  • 文档

  • Prelude

  • 模块

    • path — 指定模块的文件名。
  • 限制

  • 运行时

  • 功能

    • feature — 用于启用不稳定或实验性编译器功能。有关 rustc 中实现的功能,请参阅不稳定之书
  • 类型系统

    • non_exhaustive — 指示类型将来会添加更多字段/变体。
  • 调试器

测试属性

以下属性用于指定执行测试的函数。在“测试“模式下编译 crate 会启用构建测试函数以及执行测试的测试工具。启用测试模式还会启用 test 条件编译选项

test 属性

test 属性 将函数标记为要作为测试执行。

Example

#![allow(unused)]
fn main() {
pub fn add(left: u64, right: u64) -> u64 { left + right }
#[test]
fn it_works() {
    let result = add(2, 2);
    assert_eq!(result, 4);
}
}

test 属性使用 MetaWord 语法。

test 属性只能应用于自由函数,这些函数必须是单态的、不带参数,并且返回类型实现 Termination trait。

Note

实现 Termination trait 的一些类型包括:

  • ()
  • Result<T, E> where T: Termination, E: Debug

只有函数上的第一次使用 test 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

test 属性从标准库 prelude 导出为 std::prelude::v1::test

这些函数仅在测试模式下编译。

Note

通过将 --test 参数传递给 rustc 或使用 cargo test 来启用测试模式。

测试工具调用返回值的 report 方法,并根据结果 ExitCode 是否表示成功终止来将测试分类为通过或失败。 特别是:

  • 返回 () 的测试只要终止且不 panic 就通过。
  • 返回 Result<(), E> 的测试只要返回 Ok(()) 就通过。
  • 返回 ExitCode::SUCCESS 的测试通过,返回 ExitCode::FAILURE 的测试失败。
  • 不终止的测试既不通过也不失败。

Example

#![allow(unused)]
fn main() {
use std::io;
fn setup_the_thing() -> io::Result<i32> { Ok(1) }
fn do_the_thing(s: &i32) -> io::Result<()> { Ok(()) }
#[test]
fn test_the_thing() -> io::Result<()> {
    let state = setup_the_thing()?; // expected to succeed
    do_the_thing(&state)?;          // expected to succeed
    Ok(())
}
}

ignore 属性

ignore 属性 可与 test 属性一起使用,告诉测试工具不要将该函数作为测试执行。

Example

#![allow(unused)]
fn main() {
#[test]
#[ignore]
fn check_thing() {
    // …
}
}

Note

rustc 测试工具支持 --include-ignored 标志以强制运行被忽略的测试。

ignore 属性使用 MetaWordMetaNameValueStr 语法。

ignore 属性的 MetaNameValueStr 形式提供了一种指定测试被忽略原因的方式。

Example

#![allow(unused)]
fn main() {
#[test]
#[ignore = "not yet implemented"]
fn mytest() {
    // …
}
}

ignore 属性只能应用于带有 test 属性注释的函数。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

只有函数上的第一次使用 ignore 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

被忽略的测试在测试模式下仍然被编译,但不会被执行。

should_panic 属性

should_panic 属性 仅在应用该属性的测试函数 panic 时才使测试通过。

Example

#![allow(unused)]
fn main() {
#[test]
#[should_panic(expected = "values don't match")]
fn mytest() {
    assert_eq!(1, 2, "values don't match");
}
}

should_panic 属性具有以下形式:

  • MetaWord

    Example

    #![allow(unused)]
    fn main() {
    #[test]
    #[should_panic]
    fn mytest() { panic!("error: some message, and more"); }
    }
  • MetaNameValueStr — 给定的字符串必须出现在 panic 消息中,测试才能通过。

    Example

    #![allow(unused)]
    fn main() {
    #[test]
    #[should_panic = "some message"]
    fn mytest() { panic!("error: some message, and more"); }
    }
  • MetaListNameValueStr — 与 MetaNameValueStr 语法一样,给定的字符串必须出现在 panic 消息中。

    Example

    #![allow(unused)]
    fn main() {
    #[test]
    #[should_panic(expected = "some message")]
    fn mytest() { panic!("error: some message, and more"); }
    }

should_panic 属性只能应用于带有 test 属性注释的函数。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

只有函数上的第一次使用 should_panic 才有效果。

Note

rustc 会对第一次之后的任何使用发出带有未来兼容性警告的 lint 警告。这可能在未来成为错误。

当使用 MetaNameValueStr 形式或带有 expected 键的 MetaListNameValueStr 形式时,给定的字符串必须出现在 panic 消息中的某处,测试才能通过。

测试函数的返回类型必须是 ()

派生

derive 属性 调用一个或多个派生宏,允许为数据结构自动生成新的。您可以使用过程宏创建 derive 宏。

Example

PartialEq 派生宏为 Foo<T> where T: PartialEq 发出 PartialEq实现Clone 派生宏同样为 Clone 做同样的事情。

#![allow(unused)]
fn main() {
#[derive(PartialEq, Clone)]
struct Foo<T> {
    a: i32,
    b: T,
}
}

生成的 impl 项等同于:

#![allow(unused)]
fn main() {
struct Foo<T> { a: i32, b: T }
impl<T: PartialEq> PartialEq for Foo<T> {
    fn eq(&self, other: &Foo<T>) -> bool {
        self.a == other.a && self.b == other.b
    }
}

impl<T: Clone> Clone for Foo<T> {
    fn clone(&self) -> Self {
        Foo { a: self.a.clone(), b: self.b.clone() }
    }
}
}

derive 属性使用 MetaListPaths 语法指定要调用的派生宏路径列表。

derive 属性只能应用于结构体枚举联合体

derive 属性可以在一个项上使用任意次数。所有属性中列出的所有派生宏都会被调用。

derive 属性在标准库中导出为:

内置派生在语言 prelude 中定义。内置派生的列表是:

内置派生在其生成的实现上包含 automatically_derived 属性

在宏展开期间,对于派生列表中的每个元素,相应的派生宏展开为零个或多个

automatically_derived 属性

automatically_derived 属性 用于注释实现以指示它是由派生宏自动创建的。它没有直接影响,但工具和诊断 lint 可能使用它来检测这些自动生成的实现。

Example

给定 #[derive(Clone)]struct Example 上,派生宏可能产生:

#![allow(unused)]
fn main() {
struct Example;
#[automatically_derived]
impl ::core::clone::Clone for Example {
    #[inline]
    fn clone(&self) -> Self {
        Example
    }
}
}

automatically_derived 属性使用 MetaWord 语法。

automatically_derived 属性只能应用于实现

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

在实现上多次使用 automatically_derived 与使用一次效果相同。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

automatically_derived 属性没有行为。

诊断属性

以下属性用于在编译期间控制或生成诊断消息。

Lint 检查属性

Lint 检查命名潜在不需要的编码模式,例如不可达代码或省略的文档。

Lint 属性 allowexpectwarndenyforbid 使用 MetaListPaths 语法指定 lint 名称列表,以更改属性应用到的实体的 lint 级别。

对于任何 lint 检查 C

  • #[allow(C)] 覆盖对 C 的检查,使违规行为不会被报告。
  • #[expect(C)] 表示预期会发出 lint C。如果预期未满足,该属性将抑制 C 的发出或发出警告。
  • #[warn(C)]C 的违规发出警告,但继续编译。
  • #[deny(C)] 在遇到 C 的违规后发出错误信号,
  • #[forbid(C)]deny(C) 相同,但也禁止之后更改 lint 级别,

Note

rustc 支持的 lint 检查可以通过 rustc -W help 找到,以及它们的默认设置,并在 rustc 之书中记录。

#![allow(unused)]
fn main() {
pub mod m1 {
    // Missing documentation is ignored here
    #[allow(missing_docs)]
    pub fn undocumented_one() -> i32 { 1 }

    // Missing documentation signals a warning here
    #[warn(missing_docs)]
    pub fn undocumented_too() -> i32 { 2 }

    // Missing documentation signals an error here
    #[deny(missing_docs)]
    pub fn undocumented_end() -> i32 { 3 }
}
}

Lint 属性可以覆盖先前属性指定的级别,只要该级别不尝试更改被禁止的 lint(deny 除外,它在 forbid 上下文中是允许的,但被忽略)。先前的属性是来自语法树中更高级别的属性,或来自与左到右源顺序中列出的同一实体上的先前属性。

此示例显示如何使用 allowwarn 来切换特定检查的开启和关闭:

#![allow(unused)]
fn main() {
#[warn(missing_docs)]
pub mod m2 {
    #[allow(missing_docs)]
    pub mod nested {
        // Missing documentation is ignored here
        pub fn undocumented_one() -> i32 { 1 }

        // Missing documentation signals a warning here,
        // despite the allow above.
        #[warn(missing_docs)]
        pub fn undocumented_two() -> i32 { 2 }
    }

    // Missing documentation signals a warning here
    pub fn undocumented_too() -> i32 { 3 }
}
}

此示例显示如何使用 forbid 来禁止对该 lint 检查使用 allowexpect

#![allow(unused)]
fn main() {
#[forbid(missing_docs)]
pub mod m3 {
    // Attempting to toggle warning signals an error here
    #[allow(missing_docs)]
    /// Returns 2.
    pub fn undocumented_too() -> i32 { 2 }
}
}

Note

rustc 允许在命令行上设置 lint 级别,还支持设置上限报告的 lint。

Lint 原因

所有 lint 属性都支持额外的 reason 参数,以提供添加特定属性的原因。如果 lint 在定义的级别发出,此原因将作为 lint 消息的一部分显示。

#![allow(unused)]
fn main() {
// `keyword_idents` is allowed by default. Here we deny it to
// avoid migration of identifiers when we update the edition.
#![deny(
    keyword_idents,
    reason = "we want to avoid these idents to be future compatible"
)]

// This name was allowed in Rust's 2015 edition. We still aim to avoid
// this to be future compatible and not confuse end users.
fn dyn() {}
}

这是另一个示例,其中 lint 被允许并带有原因:

#![allow(unused)]
fn main() {
use std::path::PathBuf;

pub fn get_path() -> PathBuf {
    // The `reason` parameter on `allow` attributes acts as documentation for the reader.
    #[allow(unused_mut, reason = "this is only modified on some platforms")]
    let mut file_name = PathBuf::from("git");

    #[cfg(target_os = "windows")]
    file_name.set_extension("exe");

    file_name
}
}

#[expect] 属性

#[expect(C)] 属性为 lint C 创建 lint 预期。如果在同一位置的 #[warn(C)] 属性会导致 lint 发出,则预期将被满足。如果预期未满足,因为 lint C 不会被发出,则 unfulfilled_lint_expectations lint 将在属性处发出。

fn main() {
    // This `#[expect]` attribute creates a lint expectation, that the `unused_variables`
    // lint would be emitted by the following statement. This expectation is
    // unfulfilled, since the `question` variable is used by the `println!` macro.
    // Therefore, the `unfulfilled_lint_expectations` lint will be emitted at the
    // attribute.
    #[expect(unused_variables)]
    let question = "who lives in a pineapple under the sea?";
    println!("{question}");

    // This `#[expect]` attribute creates a lint expectation that will be fulfilled, since
    // the `answer` variable is never used. The `unused_variables` lint, that would usually
    // be emitted, is suppressed. No warning will be issued for the statement or attribute.
    #[expect(unused_variables)]
    let answer = "SpongeBob SquarePants!";
}

Lint 预期仅由被 expect 属性抑制的 lint 发出满足。如果 lint 级别在作用域中被其他级别属性(如 allowwarn)修改,lint 发出将相应处理,预期将保持未满足。

#![allow(unused)]
fn main() {
#[expect(unused_variables)]
fn select_song() {
    // This will emit the `unused_variables` lint at the warn level
    // as defined by the `warn` attribute. This will not fulfill the
    // expectation above the function.
    #[warn(unused_variables)]
    let song_name = "Crab Rave";

    // The `allow` attribute suppresses the lint emission. This will not
    // fulfill the expectation as it has been suppressed by the `allow`
    // attribute and not the `expect` attribute above the function.
    #[allow(unused_variables)]
    let song_creator = "Noisestorm";

    // This `expect` attribute will suppress the `unused_variables` lint emission
    // at the variable. The `expect` attribute above the function will still not
    // be fulfilled, since this lint emission has been suppressed by the local
    // expect attribute.
    #[expect(unused_variables)]
    let song_version = "Monstercat Release";
}
}

如果 expect 属性包含多个 lint,则每个 lint 单独预期。对于 lint 组,如果组内的一个 lint 已被发出就足够了:

#![allow(unused)]
fn main() {
// This expectation will be fulfilled by the unused value inside the function
// since the emitted `unused_variables` lint is inside the `unused` lint group.
#[expect(unused)]
pub fn thoughts() {
    let unused = "I'm running out of examples";
}

pub fn another_example() {
    // This attribute creates two lint expectations. The `unused_mut` lint will be
    // suppressed and with that fulfill the first expectation. The `unused_variables`
    // wouldn't be emitted, since the variable is used. That expectation will therefore
    // be unsatisfied, and a warning will be emitted.
    #[expect(unused_mut, unused_variables)]
    let mut link = "https://www.rust-lang.org/";

    println!("Welcome to our community: {link}");
}
}

Note

#[expect(unfulfilled_lint_expectations)] 的行为目前定义为始终生成 unfulfilled_lint_expectations lint。

Lint 组

Lint 可以组织成命名组,以便可以一起调整相关 lint 的级别。使用命名组等同于列出该组内的 lint。

#![allow(unused)]
fn main() {
// This allows all lints in the "unused" group.
#[allow(unused)]
// This overrides the "unused_must_use" lint from the "unused"
// group to deny.
#[deny(unused_must_use)]
fn example() {
    // This does not generate a warning because the "unused_variables"
    // lint is in the "unused" group.
    let x = 1;
    // This generates an error because the result is unused and
    // "unused_must_use" is marked as "deny".
    std::fs::remove_file("some_file"); // ERROR: unused `Result` that must be used
}
}

有一个名为 “warnings” 的特殊组,包含 “warn” 级别的所有 lint。“warnings” 组忽略属性顺序,并适用于实体内所有其他会发出警告的 lint。

#![allow(unused)]
fn main() {
unsafe fn an_unsafe_fn() {}
// The order of these two attributes does not matter.
#[deny(warnings)]
// The unsafe_code lint is normally "allow" by default.
#[warn(unsafe_code)]
fn example_err() {
    // This is an error because the `unsafe_code` warning has
    // been lifted to "deny".
    unsafe { an_unsafe_fn() } // ERROR: use of `unsafe` block
}
}

工具 lint 属性

工具 lint 允许使用作用域 lint,以 allowwarndenyforbid 特定工具的 lint。

工具 lint 仅在关联工具处于活动状态时才被检查。如果 lint 属性(如 allow)引用了不存在的工具 lint,编译器在您使用该工具之前不会对不存在的 lint 发出警告。

否则,它们的工作方式与常规 lint 属性相同:

// set the entire `pedantic` clippy lint group to warn
#![warn(clippy::pedantic)]
// silence warnings from the `filter_map` clippy lint
#![allow(clippy::filter_map)]

fn main() {
    // ...
}

// silence the `cmp_nan` clippy lint just for this function
#[allow(clippy::cmp_nan)]
fn foo() {
    // ...
}

Note

rustc 目前识别 “clippy” 和 “rustdoc” 的工具 lint。

deprecated 属性

deprecated 属性将项标记为已弃用。rustc 将对使用 #[deprecated] 项发出警告。rustdoc 将显示项弃用,包括 since 版本和 note(如果可用)。

deprecated 属性有几种形式:

  • deprecated — 发出通用消息。
  • deprecated = "message" — 在弃用消息中包含给定字符串。
  • MetaListNameValueStr 语法,有两个可选字段:
    • since — 指定项被弃用时的版本号。rustc 目前不解释该字符串,但 Clippy 等外部工具可能会检查值的有效性。
    • note — 指定应包含在弃用消息中的字符串。这通常用于提供关于弃用和首选替代方案的解释。

deprecated 属性可应用于任何trait 项枚举变体结构体字段外部块项宏定义。它不能应用于 trait 实现项。当应用于包含其他项的项(如模块实现)时,所有子项继承弃用属性。

这是一个示例:

#![allow(unused)]
fn main() {
#[deprecated(since = "5.2.0", note = "foo was rarely used. Users should instead use bar")]
pub fn foo() {}

pub fn bar() {}
}

RFC 包含动机和更多详细信息。

must_use 属性

*must_use [属性]*标记应该被使用的值。

must_use 属性使用 MetaWordMetaNameValueStr 语法。

Example

#![allow(unused)]
fn main() {
#[must_use]
fn use_me1() -> u8 { 0 }

#[must_use = "explanation of why it should be used"]
fn use_me2() -> u8 { 0 }
}

must_use 属性可应用于:

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

must_use 属性只能在项上使用一次。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

must_use 属性可以使用 MetaNameValueStr 语法包含消息,例如 #[must_use = "example message"]。该消息可能作为 lint 的一部分发出。

当属性应用于结构体枚举联合体时,如果表达式语句表达式具有该类型,则使用会触发 unused_must_use lint。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
struct MustUse();
MustUse(); // ERROR: Unused value that must be used.
}

作为 attributes.diagnostics.must_use.type 的例外,当 E无人居住的时,Result<(), E> 不会触发 lint,当 B无人居住的时,ControlFlow<B, ()> 也不会触发。来自外部 crate 的 #[non_exhaustive] 类型不被视为无人居住,因为它将来可能会获得构造函数。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
use core::ops::ControlFlow;
enum Empty {}
fn f1() -> Result<(), Empty> { Ok(()) }
f1(); // OK: `Empty` is uninhabited.
fn f2() -> ControlFlow<Empty, ()> { ControlFlow::Continue(()) }
f2(); // OK: `Empty` is uninhabited.
}

如果表达式语句表达式调用表达式方法调用表达式,其函数操作数是应用了属性的函数,则使用会触发 unused_must_use lint。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
fn f() {}
f(); // ERROR: Unused return value that must be used.
}

如果表达式语句表达式调用表达式方法调用表达式,其函数操作数是返回 impl traitdyn trait 类型的函数,其中约束中的一个或多个 trait 被标记了该属性,则使用会触发 unused_must_use lint。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
trait Tr {}
impl Tr for () {}
fn f() -> impl Tr {}
f(); // ERROR: Unused implementor that must be used.
}

当属性应用于 trait 声明中的函数时,attributes.diagnostics.must_use.fn 中描述的规则也适用于调用表达式方法调用表达式的函数操作数是该函数的实现时。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
trait Tr {
    #[must_use]
    fn use_me(&self);
}

impl Tr for () {
    fn use_me(&self) {}
}

().use_me(); // ERROR: Unused return value that must be used.
}
#![allow(unused)]
fn main() {
#![deny(unused_must_use)]
trait Tr {
    #[must_use]
    fn use_me(&self);
}

impl Tr for () {
    fn use_me(&self) {}
}

<() as Tr>::use_me(&());
//          ^^^^^^^^^^^ ERROR: Unused return value that must be used.
}

在检查表达式语句表达式是否符合 attributes.diagnostics.must_use.typeattributes.diagnostics.must_use.fnattributes.diagnostics.must_use.traitattributes.diagnostics.must_use.trait-function 时,lint 会透视块表达式(包括 [unsafe 块]和标记块表达式)到每个块的尾部表达式。这适用于嵌套块表达式的递归。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
fn f() {}

{ f() };        // ERROR: The lint looks through block expressions.
unsafe { f() }; // ERROR: The lint looks through `unsafe` blocks.
{ { f() } };    // ERROR: The lint looks through nested blocks.
}

在 trait 实现中的函数上使用时,该属性不起作用。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
trait Tr {
    fn f(&self);
}

impl Tr for () {
    #[must_use] // This has no effect.
    fn f(&self) {}
}

().f(); // OK.
}

Note

rustc 会对 trait 实现中的函数上的使用发出 lint 警告。这可能在未来成为错误。

Note

在某些表达式中包装 #[must_use] 函数的结果可以抑制基于 fn 的检查,因为表达式语句表达式不是对 #[must_use 函数的调用表达式方法调用表达式。如果整体表达式的类型是 #[must_use],则基于类型的检查仍然适用。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
fn f() {}

// The fn-based check does not fire for any of these, because the
// expression of the expression statement is not a call to a
// `#[must_use]` function.
(f(),);                    // Expression is a tuple, not a call.
Some(f());                 // Callee `Some` is not `#[must_use]`.
if true { f() } else {};   // Expression is an `if`, not a call.
match true {               // Expression is a `match`, not a call.
    _ => f()
};
}
#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
struct MustUse;
fn g() -> MustUse { MustUse }

// Despite the `if` expression not being a call, the type-based check
// fires because the type of the expression is `MustUse`, which has
// the `#[must_use]` attribute.
if true { g() } else { MustUse }; // ERROR: Must be used.
}

Note

当故意丢弃必须使用的值时,使用带有 _ 模式的 let 语句解构赋值是惯用做法。

#![allow(unused)]
#![deny(unused_must_use)]
fn main() {
#[must_use]
fn f() {}
let _ = f(); // OK.
_ = f(); // OK.
}

diagnostic 工具属性命名空间

#[diagnostic] 属性命名空间是用于影响编译时错误消息的属性的家园。 这些属性提供的提示不保证会被使用。

此命名空间中的未知属性被接受,尽管它们可能会对未使用的属性发出警告。 此外,已知属性的无效输入通常是警告(有关详细信息,请参阅属性定义)。 这是为了允许将来添加或丢弃属性和更改输入,而无需保持无意义的属性或选项工作。

diagnostic::on_unimplemented 属性

#[diagnostic::on_unimplemented] 属性是对编译器的提示,用于补充在需要 trait 但类型未实现 trait 的场景中通常生成的错误消息。

该属性应放在 trait 声明上,但位于其他位置不是错误。

该属性使用 MetaListNameValueStr 语法指定其输入,但属性的任何格式错误的输入都不被视为错误,以提供向前和向后兼容性。

以下键具有给定含义:

  • message — 顶级错误消息的文本。
  • label — 错误消息中损坏代码内联显示的标签的文本。
  • note — 提供额外的注释。

note 选项可以出现多次,这会导致发出多个注释消息。

如果任何其他选项出现多次,相关选项的第一次出现指定实际使用的值。后续出现会生成警告。

对任何未知键会生成警告。

所有三个选项都接受字符串作为参数,使用与 std::fmt 字符串相同的格式进行解释。

具有给定命名参数的格式参数将被替换为以下文本:

  • {Self} — 实现 trait 的类型的名称。
  • { GenericParameterName } — 给定泛型参数的泛型参数类型的名称。

任何其他格式参数将生成警告,但会按原样包含在字符串中。

无效的格式字符串可能会生成警告,但否则是允许的,但可能不会按预期显示。 格式说明符可能会生成警告,但否则会被忽略。

在此示例中:

#[diagnostic::on_unimplemented(
    message = "My Message for `ImportantTrait<{A}>` implemented for `{Self}`",
    label = "My Label",
    note = "Note 1",
    note = "Note 2"
)]
trait ImportantTrait<A> {}

fn use_my_trait(_: impl ImportantTrait<i32>) {}

fn main() {
    use_my_trait(String::new());
}

编译器可能生成如下所示的错误消息:

error[E0277]: My Message for `ImportantTrait<i32>` implemented for `String`
  --> src/main.rs:14:18
   |
14 |     use_my_trait(String::new());
   |     ------------ ^^^^^^^^^^^^^ My Label
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `ImportantTrait<i32>` is not implemented for `String`
   = note: Note 1
   = note: Note 2

diagnostic::do_not_recommend 属性

#[diagnostic::do_not_recommend] 属性是对编译器的提示,不要将注释的 trait 实现显示为诊断消息的一部分。

Note

如果您知道该建议通常对程序员没有用处,则抑制该建议可能很有用。这通常发生在广泛的、全面的 impl 中。该建议可能会将程序员引向错误的道路,或者 trait 实现可能是您不想暴露的内部细节,或者约束可能无法由程序员满足。

例如,在关于类型未实现所需 trait 的错误消息中,编译器可能会找到一个 trait 实现,如果不是 trait 实现中的特定约束,该实现将满足要求。编译器可能会告诉用户有一个 impl,但问题是 trait 实现中的约束。#[diagnostic::do_not_recommend] 属性可用于告诉编译器不要告诉用户有关 trait 实现的信息,而是简单地告诉用户该类型未实现所需的 trait。

该属性应放在 trait 实现项上,但位于其他位置不是错误。

该属性不接受任何参数,但意外参数不被视为错误。

在以下示例中,有一个名为 AsExpression 的 trait,用于将任意类型转换为 SQL 库中使用的 Expression 类型。有一个名为 check 的方法,它接受一个 AsExpression

pub trait Expression {
    type SqlType;
}

pub trait AsExpression<ST> {
    type Expression: Expression<SqlType = ST>;
}

pub struct Text;
pub struct Integer;

pub struct Bound<T>(T);
pub struct SelectInt;

impl Expression for SelectInt {
    type SqlType = Integer;
}

impl<T> Expression for Bound<T> {
    type SqlType = T;
}

impl AsExpression<Integer> for i32 {
    type Expression = Bound<Integer>;
}

impl AsExpression<Text> for &'_ str {
    type Expression = Bound<Text>;
}

impl<T> Foo for T where T: Expression {}

// Uncomment this line to change the recommendation.
// #[diagnostic::do_not_recommend]
impl<T, ST> AsExpression<ST> for T
where
    T: Expression<SqlType = ST>,
{
    type Expression = T;
}

trait Foo: Expression + Sized {
    fn check<T>(&self, _: T) -> <T as AsExpression<<Self as Expression>::SqlType>>::Expression
    where
        T: AsExpression<Self::SqlType>,
    {
        todo!()
    }
}

fn main() {
    SelectInt.check("bar");
}

SelectInt 类型的 check 方法期望一个 Integer 类型。用 i32 类型调用它可以工作,因为它通过 AsExpression trait 转换为 Integer。但是,用字符串调用它不行,并生成如下所示的错误:

error[E0277]: the trait bound `&str: Expression` is not satisfied
  --> src/main.rs:53:15
   |
53 |     SelectInt.check("bar");
   |               ^^^^^ the trait `Expression` is not implemented for `&str`
   |
   = help: the following other types implement trait `Expression`:
             Bound<T>
             SelectInt
note: required for `&str` to implement `AsExpression<Integer>`
  --> src/main.rs:45:13
   |
45 | impl<T, ST> AsExpression<ST> for T
   |             ^^^^^^^^^^^^^^^^     ^
46 | where
47 |     T: Expression<SqlType = ST>,
   |        ------------------------ unsatisfied trait bound introduced here

通过将 #[diagnostic::do_not_recommend] 属性添加到 AsExpression 的全面实现 impl,消息更改为:

error[E0277]: the trait bound `&str: AsExpression<Integer>` is not satisfied
  --> src/main.rs:53:15
   |
53 |     SelectInt.check("bar");
   |               ^^^^^ the trait `AsExpression<Integer>` is not implemented for `&str`
   |
   = help: the trait `AsExpression<Integer>` is not implemented for `&str`
           but trait `AsExpression<Text>` is implemented for it
   = help: for that trait implementation, expected `Text`, found `Integer`

第一个错误消息包含关于 &strExpression 关系的令人困惑的错误消息,以及全面实现中未满足的 trait 约束。添加 #[diagnostic::do_not_recommend] 后,它不再考虑全面实现作为建议。消息应该更清晰一些,指示字符串无法转换为 Integer

代码生成属性

以下属性用于控制代码生成。

inline 属性

*inline [属性]*建议是否应将属性函数代码的副本放置在调用者中,而不是生成对函数的调用。

Example

#![allow(unused)]
fn main() {
#[inline]
pub fn example1() {}

#[inline(always)]
pub fn example2() {}

#[inline(never)]
pub fn example3() {}
}

Note

rustc 在这样做似乎值得时会自动内联函数。请谨慎使用此属性,因为关于内联什么的错误决定可能会使程序变慢。

inline 属性的语法是:

Syntax
InlineAttribute
      inline ( always )
    | inline ( never )
    | inline

InlineAttribute inline ( always ) inline ( never ) inline

inline 属性只能应用于具有主体的函数 — 闭包异步块自由函数固有实现trait 实现中的关联函数,以及当这些函数具有默认定义trait 定义中的关联函数。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

Note

虽然该属性可以应用于闭包异步块,但其用处有限,因为我们尚不支持表达式上的属性。

#![allow(unused)]
fn main() {
// We allow attributes on statements.
#[inline] || (); // OK
#[inline] async {}; // OK
}
#![allow(unused)]
fn main() {
// We don't yet allow attributes on expressions.
let f = #[inline] || (); // ERROR
}

只有函数上的第一次使用 inline 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

inline 属性支持以下模式:

  • #[inline] 建议执行内联展开。
  • #[inline(always)] 建议应始终执行内联展开。
  • #[inline(never)] 建议应从不执行内联展开。

Note

在每种形式中,该属性都是提示。编译器可能会忽略它。

inline 应用于 trait 中的函数时,它仅适用于默认定义的代码。

inline 应用于异步函数异步闭包时,它仅适用于生成的 poll 函数的代码。

Note

有关更多详细信息,请参阅 Rust issue #129347

如果函数通过 no_mangleexport_name 外部导出,则 inline 属性被忽略。

cold 属性

*cold [属性]*建议属性函数不太可能被调用,这可能有助于编译器生成更好的代码。

Example

#![allow(unused)]
fn main() {
#[cold]
pub fn example() {}
}

cold 属性使用 MetaWord 语法。

cold 属性只能应用于具有主体的函数 — 闭包异步块自由函数固有实现trait 实现中的关联函数,以及当这些函数具有默认定义trait 定义中的关联函数。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

Note

虽然该属性可以应用于闭包异步块,但其用处有限,因为我们尚不支持表达式上的属性。

只有函数上的第一次使用 cold 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

cold 应用于 trait 中的函数时,它仅适用于默认定义的代码。

naked 属性

*naked [属性]*阻止编译器为属性函数发出函数序言和尾声 — 裸函数

Example

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
/// Adds 3 to the given number.
// SAFETY: The body respects the "sysv64" calling convention,
// upholds the signature, and does not fall through.
#[unsafe(naked)]
pub extern "sysv64" fn add_n(number: u64) -> u64 {
    core::arch::naked_asm!(
        "add rdi, {}",
        "mov rax, rdi",
        "ret",
        const 3,
    )
}
}
}

naked 属性使用 MetaWord 语法。

naked 属性只能应用于自由函数固有实现trait 实现中的关联函数,以及当这些函数具有默认定义trait 定义中的关联函数。

只有函数上的第一次使用 naked 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

naked 属性必须用 unsafe 标记,因为主体必须尊重函数的调用约定、维护其签名,并且要么返回要么发散(即不会穿透汇编代码末尾)。

函数主体必须恰好包含一个 naked_asm! 宏调用。

编译器不为裸函数发出序言或尾声:naked_asm! 调用中的汇编代码构成其整个主体。

在入口处,汇编代码可以假设调用栈和寄存器状态根据函数的签名和调用约定是有效的。

编译器不得复制汇编代码,除非在单态化多态函数时。

Note

此保证对于定义符号的裸函数很重要。

unused_variables lint 在裸函数中被抑制。

[inline 属性]不能应用于裸函数。

[track_caller 属性]不能应用于裸函数。

[测试属性]不能应用于裸函数。

[target_feature 属性]不能应用于裸函数。

裸函数不能使用 “Rust” ABI

no_builtins 属性

*no_builtins [属性]*禁用对假定存在的库函数调用的某些代码模式的优化。

Example

#![allow(unused)]
#![no_builtins]
fn main() {
}

no_builtins 属性使用 MetaWord 语法。

no_builtins 属性只能应用于 crate 根。

只有第一次使用 no_builtins 属性才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

target_feature 属性

*target_feature [属性]*可应用于函数,以启用该函数针对特定平台架构功能的代码生成。它使用 MetaListNameValueStr 语法,单个键为 enable,其值为逗号分隔的功能名称字符串。

#![allow(unused)]
fn main() {
#[cfg(target_feature = "avx2")]
#[target_feature(enable = "avx2")]
fn foo_avx2() {}
}

每个目标架构都有一组可以启用的功能。为 crate 未编译的目标架构指定功能是错误的。

target_feature 注释的函数内定义的闭包从封闭函数继承该属性。

调用编译时启用的功能在当前运行平台上不受支持的函数是未定义行为除非平台明确记录这是安全的。

除非下面的平台规则另有规定,否则适用以下限制:

  • 安全的 #[target_feature] 函数(以及继承该属性的闭包)只能在启用所有被调用者启用的 target_feature 的调用者中安全调用。 此限制不适用于 unsafe 上下文。
  • 安全的 #[target_feature] 函数(以及继承该属性的闭包)只能在启用所有被强制转换者启用的 target_feature 的上下文中强制转换为安全函数指针。 此限制不适用于 unsafe 函数指针。

隐式启用的功能包含在此规则中。例如,sse2 函数可以调用标记为 sse 的函数。

#![allow(unused)]
fn main() {
#[cfg(target_feature = "sse2")] {
#[target_feature(enable = "sse")]
fn foo_sse() {}

fn bar() {
    // Calling `foo_sse` here is unsafe, as we must ensure that SSE is
    // available first, even if `sse` is enabled by default on the target
    // platform or manually enabled as compiler flags.
    unsafe {
        foo_sse();
    }
}

#[target_feature(enable = "sse")]
fn bar_sse() {
    // Calling `foo_sse` here is safe.
    foo_sse();
    || foo_sse();
}

#[target_feature(enable = "sse2")]
fn bar_sse2() {
    // Calling `foo_sse` here is safe because `sse2` implies `sse`.
    foo_sse();
}
}
}

具有 #[target_feature] 属性的函数从不实现 Fn 系列 trait,但从封闭函数继承功能的闭包除外。

#[target_feature] 属性不允许在以下位置:

标记为 target_feature 的函数不会内联到不支持给定功能的上下文中。#[inline(always)] 属性不能与 target_feature 属性一起使用。

可用功能

以下是可用功能名称的列表。

x86x86_64

在此平台上,使用不受支持的功能执行代码是未定义行为。因此,在此平台上使用 #[target_feature] 函数遵循上述限制

功能隐式启用描述
adxADX — 多精度加法进位指令扩展
aessse2AES — 高级加密标准
avxsse4.2AVX — 高级向量扩展
avx2avxAVX2 — 高级向量扩展 2
avx512bf16avx512bwAVX512-BF16 — 高级向量扩展 512 位 - Bfloat16 扩展
avx512bitalgavx512bwAVX512-BITALG — 高级向量扩展 512 位 - 位算法
avx512bwavx512fAVX512-BW — 高级向量扩展 512 位 - 字节和字指令
avx512cdavx512fAVX512-CD — 高级向量扩展 512 位 - 冲突检测指令
avx512dqavx512fAVX512-DQ — 高级向量扩展 512 位 - 双字和四字指令
avx512favx2, fma, f16cAVX512-F — 高级向量扩展 512 位 - 基础
avx512fp16avx512bwAVX512-FP16 — 高级向量扩展 512 位 - Float16 扩展
avx512ifmaavx512fAVX512-IFMA — 高级向量扩展 512 位 - 整数融合乘加
avx512vbmiavx512bwAVX512-VBMI — 高级向量扩展 512 位 - 向量字节操作指令
avx512vbmi2avx512bwAVX512-VBMI2 — 高级向量扩展 512 位 - 向量字节操作指令 2
avx512vlavx512fAVX512-VL — 高级向量扩展 512 位 - 向量长度扩展
avx512vnniavx512fAVX512-VNNI — 高级向量扩展 512 位 - 向量神经网络指令
avx512vp2intersectavx512fAVX512-VP2INTERSECT — 高级向量扩展 512 位 - 向量对交集到一对掩码寄存器
avx512vpopcntdqavx512fAVX512-VPOPCNTDQ — 高级向量扩展 512 位 - 向量总体计数指令
avxifmaavx2AVX-IFMA — 高级向量扩展 - 整数融合乘加
avxneconvertavx2AVX-NE-CONVERT — 高级向量扩展 - 无异常浮点转换指令
avxvnniavx2AVX-VNNI — 高级向量扩展 - 向量神经网络指令
avxvnniint16avx2AVX-VNNI-INT16 — 高级向量扩展 - 16 位整数向量神经网络指令
avxvnniint8avx2AVX-VNNI-INT8 — 高级向量扩展 - 8 位整数向量神经网络指令
bmi1BMI1 — 位操作指令集
bmi2BMI2 — 位操作指令集 2
cmpxchg16bcmpxchg16b — 原子比较和交换 16 字节(128 位)数据
f16cavxF16C — 16 位浮点转换指令
fmaavxFMA3 — 三操作数融合乘加
fxsrfxsavefxrstor — 保存和恢复 x87 FPU、MMX 技术和 SSE 状态
gfnisse2GFNI — 伽罗瓦域新指令
klsse2KEYLOCKER — Intel 密钥锁指令
lzcntlzcnt — 前导零计数
movbemovbe — 交换字节后移动数据
pclmulqdqsse2pclmulqdq — 打包无进位乘法四字
popcntpopcnt — 设置为 1 的位计数
rdrandrdrand — 读取随机数
rdseedrdseed — 读取随机种子
shasse2SHA — 安全哈希算法
sha512avx2SHA512 — 512 位摘要的安全哈希算法
sm3avxSM3 — 商密 3 哈希算法
sm4avx2SM4 — 商密 4 密码算法
sseSSE — 流式 SIMD 扩展
sse2sseSSE2 — 流式 SIMD 扩展 2
sse3sse2SSE3 — 流式 SIMD 扩展 3
sse4.1ssse3SSE4.1 — 流式 SIMD 扩展 4.1
sse4.2sse4.1SSE4.2 — 流式 SIMD 扩展 4.2
sse4asse3SSE4a — 流式 SIMD 扩展 4a
ssse3sse3SSSE3 — 补充流式 SIMD 扩展 3
tbmTBM — 尾部位操作
vaesavx2, aesVAES — 向量 AES 指令
vpclmulqdqavx, pclmulqdqVPCLMULQDQ — 向量四字无进位乘法
wideklklKEYLOCKER_WIDE — Intel 宽密钥锁指令
xsavexsave — 保存处理器扩展状态
xsavecxsavec — 压缩保存处理器扩展状态
xsaveoptxsaveopt — 优化保存处理器扩展状态
xsavesxsaves — 监督者保存处理器扩展状态

aarch64

在此平台上使用 #[target_feature] 函数遵循上述限制

有关这些功能的进一步文档可在 ARM 架构参考手册developer.arm.com 上找到。

Note

如果使用以下功能对,应同时标记为启用或禁用:

  • pacapacg,LLVM 目前将其实现为一个功能。
功能隐式启用功能名称
aesneonFEAT_AES & FEAT_PMULL — 高级 SIMD AES 和 PMULL 指令
bf16FEAT_BF16 — BFloat16 指令
btiFEAT_BTI — 分支目标识别
crcFEAT_CRC — CRC32 校验和指令
ditFEAT_DIT — 数据独立时序指令
dotprodneonFEAT_DotProd — 高级 SIMD Int8 点积指令
dpbFEAT_DPB — 数据缓存清理到持久点
dpb2dpbFEAT_DPB2 — 数据缓存清理到深度持久点
f32mmsveFEAT_F32MM — SVE 单精度 FP 矩阵乘法指令
f64mmsveFEAT_F64MM — SVE 双精度 FP 矩阵乘法指令
fcmaneonFEAT_FCMA — 浮点复数支持
fhmfp16FEAT_FHM — 半精度 FP FMLAL 指令
flagmFEAT_FLAGM — 条件标志操作
fp16neonFEAT_FP16 — 半精度 FP 数据处理
frinttsFEAT_FRINTTS — 浮点到整数辅助指令
i8mmFEAT_I8MM — Int8 矩阵乘法
jsconvneonFEAT_JSCVT — JavaScript 转换指令
lorFEAT_LOR — 有限排序区域扩展
lseFEAT_LSE — 大型系统扩展
mteFEAT_MTE & FEAT_MTE2 — 内存标记扩展
neonFEAT_AdvSimd & FEAT_FP — 浮点和高级 SIMD 扩展
pacaFEAT_PAUTH — 指针认证(地址认证)
pacgFEAT_PAUTH — 指针认证(通用认证)
panFEAT_PAN — 特权访问禁止扩展
pmuv3FEAT_PMUv3 — 性能监视器扩展(v3)
randFEAT_RNG — 随机数生成器
rasFEAT_RAS & FEAT_RASv1p1 — 可靠性、可用性和可维护性扩展
rcpcFEAT_LRCPC — 释放一致处理器一致
rcpc2rcpcFEAT_LRCPC2 — 带立即偏移的 RcPc
rdmneonFEAT_RDM — 舍入双乘累加
sbFEAT_SB — 推测屏障
sha2neonFEAT_SHA1 & FEAT_SHA256 — 高级 SIMD SHA 指令
sha3sha2FEAT_SHA512 & FEAT_SHA3 — 高级 SIMD SHA 指令
sm4neonFEAT_SM3 & FEAT_SM4 — 高级 SIMD SM3/4 指令
speFEAT_SPE — 统计分析扩展
ssbsFEAT_SSBS & FEAT_SSBS2 — 推测存储旁路安全
sveneonFEAT_SVE — 可伸缩向量扩展
sve2sveFEAT_SVE2 — 可伸缩向量扩展 2
sve2-aessve2, aesFEAT_SVE_AES & FEAT_SVE_PMULL128 — SVE AES 指令
sve2-bitpermsve2FEAT_SVE2_BitPerm — SVE 位排列
sve2-sha3sve2, sha3FEAT_SVE2_SHA3 — SVE SHA3 指令
sve2-sm4sve2, sm4FEAT_SVE2_SM4 — SVE SM4 指令
tmeFEAT_TME — 事务内存扩展
vhFEAT_VHE — 虚拟化主机扩展

loongarch

在此平台上使用 #[target_feature] 函数遵循上述限制

功能隐式启用描述
fF — 单精度浮点指令
dfD — 双精度浮点指令
frecipeFRECIPE — 倒数近似指令
lasxlsxLASX — 256 位向量指令
lbtLBT — 二进制翻译指令
lsxdLSX — 128 位向量指令
lvzLVZ — 虚拟化指令
div32DIV32 — 接受非符号扩展 32 位操作数的除法指令
lam-bhLAM-BH — 字节和半字的原子交换和加法指令
lamcasLAMCAS — 字节、半字、字和双字的原子比较交换指令
ld-seq-saLD-SEQ-SA — 对同一地址的加载操作的顺序排序
scqSCQ — 条件存储四字指令

riscv32riscv64

在此平台上使用 #[target_feature] 函数遵循上述限制

有关这些功能的进一步文档可在各自的规范中找到。许多规范在 RISC-V ISA 手册、[版本 20250508] 或 [RISC-V GitHub 账户]上托管的其他手册中描述。

功能隐式启用描述
azaamo, zalrscA — 原子指令
bzba, zbc, zbsB — 位操作指令
czcaC — 压缩指令
mM — 整数乘法和除法指令
za64rsza128rsZa64rs — 平台行为:自然对齐的保留集 ≦ 64 字节
za128rsZa128rs — 平台行为:自然对齐的保留集 ≦ 128 字节
zaamoZaamo — 原子内存操作指令
zabhazaamoZabha — 字节和半字原子内存操作指令
zacaszaamoZacas — 原子比较交换(CAS)指令
zalrscZalrsc — 加载保留/条件存储指令
zama16bZama16b — 平台行为:不对齐的加载、存储和 AMO 到不跨越自然对齐 16 字节边界的主内存区域是原子的
zawrsZawrs — 等待保留集指令
zbaZba — 地址生成指令
zbbZbb — 基本位操作
zbczbkcZbc — 无进位乘法
zbkbZbkb — 密码学位操作指令
zbkcZbkc — 密码学无进位乘法
zbkxZbkx — 纵横排列
zbsZbs — 单位指令
zcaZca — 压缩指令:整数部分子集
zcbzcaZcb — 简单代码大小节省压缩指令
zcmopzcaZcmop — 压缩可能是操作
zic64bZic64b — 平台行为:自然对齐的 64 字节缓存块
zicbomZicbom — 缓存块管理指令
zicbopZicbop — 缓存块预取提示指令
zicbozZicboz — 缓存块清零指令
ziccamoaZiccamoa — 平台行为:可缓存和一致的主内存支持所有基本原子操作
ziccifZiccif — 平台行为:可缓存和一致的主内存支持指令获取和自然对齐的 2 的幂大小的获取是原子的
zicclsmZicclsm — 平台行为:可缓存和一致的主内存支持不对齐的加载/存储访问
ziccrseZiccrse — 平台行为:可缓存和一致的主内存保证 LR/SC 序列最终成功
zicntrzicsrZicntr — 基本计数器和定时器
zicondZicond — 整数条件操作指令
zicsrZicsr — 控制和状态寄存器(CSR)指令
zifenceiZifencei — 指令获取屏障指令
zihintntlZihintntl — 非时间局部性提示指令
zihintpauseZihintpause — 暂停提示指令
zihpmzicsrZihpm — 硬件性能计数器
zimopZimop — 可能是操作
zkzkn, zkr, zks, zkt, zbkb, zbkc, zkbxZk — 标量密码学
zknzknd, zkne, zknh, zbkb, zbkc, zkbxZkn — NIST 算法套件扩展
zkndZknd — NIST 套件:AES 解密
zkneZkne — NIST 套件:AES 加密
zknhZknh — NIST 套件:哈希函数指令
zkrZkr — 熵源扩展
zkszksed, zksh, zbkb, zbkc, zkbxZks — 商密算法套件
zksedZksed — 商密套件:SM4 分组密码指令
zkshZksh — 商密套件:SM3 哈希函数指令
zktZkt — 数据独立执行延迟子集
ztsoZtso — 全存储排序

wasm32wasm64

安全的 #[target_feature] 函数在 Wasm 平台上始终可以在安全上下文中使用。不可能通过 #[target_feature] 属性导致未定义行为,因为尝试使用 Wasm 引擎不支持的指令将在加载时失败,而不会以与编译器预期不同的方式被解释的风险。

s390x

s390x 目标上,使用具有 #[target_feature] 属性的函数遵循上述限制

有关这些功能的进一步文档可在 z/Architecture Principles of Operation 第 1 章的“Additions to z/Architecture“部分找到。

功能隐式启用描述
vector128 位向量指令
vector-enhancements-1vector向量增强 1
vector-enhancements-2vector-enhancements-1向量增强 2
vector-enhancements-3vector-enhancements-2向量增强 3
vector-packed-decimalvector向量压缩十进制
vector-packed-decimal-enhancementvector-packed-decimal向量压缩十进制增强
vector-packed-decimal-enhancement-2vector-packed-decimal-enhancement-2向量压缩十进制增强 2
vector-packed-decimal-enhancement-3vector-packed-decimal-enhancement-3向量压缩十进制增强 3
nnp-assistvectornnp 辅助
miscellaneous-extensions-2杂项扩展 2
miscellaneous-extensions-3杂项扩展 3
miscellaneous-extensions-4杂项扩展 4

附加信息

有关基于编译时设置选择性启用或禁用代码编译的信息,请参阅 target_feature 条件编译选项。请注意,此选项不受 target_feature 属性影响,仅由整个 crate 启用的功能驱动。

可以在运行时使用标准库中特定于平台的宏检查功能是否已启用,例如 is_x86_feature_detectedis_aarch64_feature_detected

Note

rustc 为每个目标和 CPU 启用了一组默认功能。可以使用 -C target-cpu 标志选择 CPU。可以使用 -C target-feature 标志为整个 crate 启用或禁用单个功能。

track_caller 属性

track_caller 属性可应用于任何具有 "Rust" ABI 的函数,入口点 fn main 除外。

当应用于 trait 声明中的函数和方法时,该属性适用于所有实现。如果 trait 提供了具有该属性的默认实现,则该属性也适用于全面实现。

当应用于 extern 块中的函数时,该属性也必须应用于任何链接的实现,否则会导致未定义行为。当应用于提供给 extern 块的函数时,extern 块中的声明也必须具有该属性,否则会导致未定义行为。

行为

将属性应用于函数 f 允许 f 内的代码获取导致 f 调用的“最顶层“跟踪调用的 Location 提示。在观察点,实现的行为就像它从 f 的帧向上遍历栈以找到最近的未属性化函数 outer 的帧,并返回 outer 中跟踪调用的 Location

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
}

Note

core 提供 core::panic::Location::caller 用于观察调用者位置。它包装了 rustc 实现的 core::intrinsics::caller_location 内部函数。

Note

因为结果 Location 是一个提示,实现可能会提前停止向上遍历栈。有关重要注意事项,请参阅限制

示例

fcalls_f 直接调用时,f 中的代码观察其在 calls_f 中的调用位置:

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
fn calls_f() {
    f(); // <-- f() prints this location
}
}

f 被另一个属性化函数 g 调用,而 g 又被 calls_g 调用时,fg 中的代码都观察 gcalls_g 中的调用位置:

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
#[track_caller]
fn g() {
    println!("{}", std::panic::Location::caller());
    f();
}

fn calls_g() {
    g(); // <-- g() prints this location twice, once itself and once from f()
}
}

g 被另一个属性化函数 h 调用,而 h 又被 calls_h 调用时,fgh 中的所有代码都观察 hcalls_h 中的调用位置:

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
#[track_caller]
fn g() {
    println!("{}", std::panic::Location::caller());
    f();
}
#[track_caller]
fn h() {
    println!("{}", std::panic::Location::caller());
    g();
}

fn calls_h() {
    h(); // <-- prints this location three times, once itself, once from g(), once from f()
}
}

依此类推。

限制

此信息是提示,实现不需要保留它。

特别是,将具有 #[track_caller] 的函数强制转换为函数指针会创建一个垫片,在观察者看来就像在属性函数的定义位置被调用一样,在虚拟调用中丢失了实际的调用者信息。这种强制转换的常见示例是创建其方法被属性化的 trait 对象。

Note

上述函数指针的垫片是必要的,因为 rustc 通过向函数 ABI 追加隐式参数在代码生成上下文中实现 track_caller,但这对于间接调用将是不健全的,因为该参数不是函数类型的一部分,并且给定的函数指针类型可能引用也可能不引用具有该属性的函数。垫片的创建对函数指针的调用者隐藏了隐式参数,保持了健全性。

instruction_set 属性

*instruction_set [属性]*指定函数在代码生成期间使用的指令集。这允许在单个程序中混合多个指令集。

Example

#[instruction_set(arm::a32)]
fn arm_code() {}

#[instruction_set(arm::t32)]
fn thumb_code() {}

instruction_set 属性使用 MetaListPaths 语法指定由架构系列名称和指令集名称组成的单个路径。

instruction_set 属性只能应用于具有主体的函数 — 闭包异步块自由函数固有实现trait 实现中的关联函数,以及当这些函数具有默认定义trait 定义中的关联函数。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

Note

虽然该属性可以应用于闭包异步块,但其用处有限,因为我们尚不支持表达式上的属性。

instruction_set 属性只能在函数上使用一次。

instruction_set 属性只能在支持给定值的目标上使用。

使用 instruction_set 属性时,函数中的任何内联汇编必须使用指定的指令集,而不是目标默认值。

ARM 上的 instruction_set

ARMv4TARMv5te 架构为目标时,instruction_set 的支持值为:

  • arm::a32 — 将函数生成为 A32 “ARM” 代码。
  • arm::t32 — 将函数生成为 T32 “Thumb” 代码。

如果将函数的地址作为函数指针获取,地址的低位将取决于所选的指令集:

  • 对于 arm::a32(“ARM”),它将是 0。
  • 对于 arm::t32(“Thumb”),它将是 1。

限制

以下属性影响编译时限制。

recursion_limit 属性

recursion_limit 属性可应用于 crate 级别,以设置潜在无限递归编译时操作(如宏展开或自动解引用)的最大深度。

它使用 MetaNameValueStr 语法指定递归深度。

Note

rustc 中的默认值为 128。

#![allow(unused)]
#![recursion_limit = "4"]

fn main() {
macro_rules! a {
    () => { a!(1); };
    (1) => { a!(2); };
    (2) => { a!(3); };
    (3) => { a!(4); };
    (4) => { };
}

// This fails to expand because it requires a recursion depth greater than 4.
a!{}
}
#![allow(unused)]
#![recursion_limit = "1"]

fn main() {
// This fails because it requires two recursive steps to auto-dereference.
(|_: &u8| {})(&&&1);
}

type_length_limit 属性

type_length_limit 属性 设置在单态化期间构造具体类型时允许的最大类型替换数量。

Note

rustc 仅在 nightly -Zenforce-type-length-limit 标志激活时才强制执行此限制。

有关更多信息,请参阅 Rust PR #127670

Example

#![type_length_limit = "4"]

fn f<T>(x: T) {}

// This fails to compile because monomorphizing to
// `f::<((((i32,), i32), i32), i32)>` requires more
// than 4 type elements.
f(((((1,), 2), 3), 4));

Note

rustc 中的默认值为 1048576

type_length_limit 属性使用 MetaNameValueStr 语法。字符串中的值必须是非负数。

type_length_limit 属性只能应用于 crate 根。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

只有项上的第一次使用 type_length_limit 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

类型系统属性

以下属性用于更改类型的使用方式。

non_exhaustive 属性

non_exhaustive 属性指示类型或变体将来可能会添加更多字段或变体。

它可以应用于结构体枚举enum 变体。

non_exhaustive 属性使用 MetaWord 语法,因此不接受任何输入。

在定义 crate 内,non_exhaustive 没有效果。

#![allow(unused)]
fn main() {
#[non_exhaustive]
pub struct Config {
    pub window_width: u16,
    pub window_height: u16,
}

#[non_exhaustive]
pub struct Token;

#[non_exhaustive]
pub struct Id(pub u64);

#[non_exhaustive]
pub enum Error {
    Message(String),
    Other,
}

pub enum Message {
    #[non_exhaustive] Send { from: u32, to: u32, contents: String },
    #[non_exhaustive] Reaction(u32),
    #[non_exhaustive] Quit,
}

// Non-exhaustive structs can be constructed as normal within the defining crate.
let config = Config { window_width: 640, window_height: 480 };
let token = Token;
let id = Id(4);

// Non-exhaustive structs can be matched on exhaustively within the defining crate.
let Config { window_width, window_height } = config;
let Token = token;
let Id(id_number) = id;

let error = Error::Other;
let message = Message::Reaction(3);

// Non-exhaustive enums can be matched on exhaustively within the defining crate.
match error {
    Error::Message(ref s) => { },
    Error::Other => { },
}

match message {
    // Non-exhaustive variants can be matched on exhaustively within the defining crate.
    Message::Send { from, to, contents } => { },
    Message::Reaction(id) => { },
    Message::Quit => { },
}
}

在定义 crate 外部,带有 non_exhaustive 注释的类型在添加新字段或变体时具有保留向后兼容性的限制。

非穷举类型无法在定义 crate 外部构造:

以下构造示例在定义 crate 外部无法编译:

// These are types defined in an upstream crate that have been annotated as
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};

// Cannot construct an instance of `Config`; if new fields were added in
// a new version of `upstream` then this would fail to compile, so it is
// disallowed.
let config = Config { window_width: 640, window_height: 480 };

// Cannot construct an instance of `Token`; if new fields were added, then
// it would not be a unit-like struct any more, so the same-named constant
// created by it being a unit-like struct is not public outside the crate;
// this code fails to compile.
let token = Token;

// Cannot construct an instance of `Id`; if new fields were added, then
// its constructor function signature would change, so its constructor
// function is not public outside the crate; this code fails to compile.
let id = Id(5);

// Can construct an instance of `Error`; new variants being introduced would
// not result in this failing to compile.
let error = Error::Message("foo".to_string());

// Cannot construct an instance of `Message::Send` or `Message::Reaction`;
// if new fields were added in a new version of `upstream` then this would
// fail to compile, so it is disallowed.
let message = Message::Send { from: 0, to: 1, contents: "foo".to_string(), };
let message = Message::Reaction(0);

// Cannot construct an instance of `Message::Quit`; if this were converted to
// a tuple enum variant `upstream`, this would fail to compile.
let message = Message::Quit;

在定义 crate 外部匹配非穷举类型时有限制:

  • 当对非穷举变体(structenum 变体)进行模式匹配时,必须使用包含 ..StructPattern。元组枚举变体构造函数的可见性被降低到不超过 pub(crate)
  • 当对非穷举 enum 进行模式匹配时,匹配变体不会对分支的穷举性有贡献。以下匹配示例在定义 crate 外部无法编译:
// These are types defined in an upstream crate that have been annotated as
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};

// Cannot match on a non-exhaustive enum without including a wildcard arm.
match error {
  Error::Message(ref s) => { },
  Error::Other => { },
  // would compile with: `_ => {},`
}

// Cannot match on a non-exhaustive struct without a wildcard.
if let Ok(Config { window_width, window_height }) = config {
    // would compile with: `..`
}

// Cannot match a non-exhaustive unit-like or tuple struct except by using
// braced struct syntax with a wildcard.
// This would compile as `let Token { .. } = token;`
let Token = token;
// This would compile as `let Id { 0: id_number, .. } = id;`
let Id(id_number) = id;

match message {
  // Cannot match on a non-exhaustive struct enum variant without including a wildcard.
  Message::Send { from, to, contents } => { },
  // Cannot match on a non-exhaustive tuple or unit enum variant.
  Message::Reaction(type) => { },
  Message::Quit => { },
}

也不允许对包含任何非穷举变体的枚举使用数值类型转换(as)。

例如,以下枚举可以被转换,因为它不包含任何非穷举变体:

#![allow(unused)]
fn main() {
#[non_exhaustive]
pub enum Example {
    First,
    Second,
}
}

但是,如果枚举包含哪怕一个非穷举变体,转换也会导致错误。考虑同一个枚举的这个修改版本:

#![allow(unused)]
fn main() {
#[non_exhaustive]
pub enum EnumWithNonExhaustiveVariants {
    First,
    #[non_exhaustive]
    Second,
}
}
use othercrate::EnumWithNonExhaustiveVariants;

// Error: cannot cast an enum with a non-exhaustive variant when it's defined in another crate
let _ = EnumWithNonExhaustiveVariants::First as u8;

非穷举类型在下游 crate 中始终被视为已居住。

调试器属性

以下属性用于增强使用第三方调试器(如 GDB 或 WinDbg)时的调试体验。

debugger_visualizer 属性

*debugger_visualizer 属性*可用于将调试器可视化文件嵌入调试信息中。这在显示值时改善调试器体验。

Example

#![debugger_visualizer(natvis_file = "Example.natvis")]
#![debugger_visualizer(gdb_script_file = "example.py")]

debugger_visualizer 属性使用 MetaListNameValueStr 语法指定其输入。必须指定以下键之一:

debugger_visualizer 属性只能应用于模块或 crate 根。

debugger_visualizer 属性可以在一个形式上使用任意次数。所有指定的可视化文件都将被加载。

debugger_visualizer 与 Natvis 一起使用

Natvis 是 Microsoft 调试器(如 Visual Studio 和 WinDbg)的基于 XML 的框架,使用声明性规则自定义类型的显示。有关 Natvis 格式的详细信息,请参阅 Microsoft 的 Natvis 文档

此属性仅支持在 -windows-msvc 目标上嵌入 Natvis 文件。

Natvis 文件的路径使用 natvis_file 键指定,该键是相对于源文件的路径。

Example

#![debugger_visualizer(natvis_file = "Rectangle.natvis")]

struct FancyRect {
    x: f32,
    y: f32,
    dx: f32,
    dy: f32,
}

fn main() {
    let fancy_rect = FancyRect { x: 10.0, y: 10.0, dx: 5.0, dy: 5.0 };
    println!("set breakpoint here");
}

Rectangle.natvis 包含:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    <Type Name="foo::FancyRect">
      <DisplayString>({x},{y}) + ({dx}, {dy})</DisplayString>
      <Expand>
        <Synthetic Name="LowerLeft">
          <DisplayString>({x}, {y})</DisplayString>
        </Synthetic>
        <Synthetic Name="UpperLeft">
          <DisplayString>({x}, {y + dy})</DisplayString>
        </Synthetic>
        <Synthetic Name="UpperRight">
          <DisplayString>({x + dx}, {y + dy})</DisplayString>
        </Synthetic>
        <Synthetic Name="LowerRight">
          <DisplayString>({x + dx}, {y})</DisplayString>
        </Synthetic>
      </Expand>
    </Type>
</AutoVisualizer>

在 WinDbg 下查看时,fancy_rect 变量将显示如下:

> Variables:
  > fancy_rect: (10.0, 10.0) + (5.0, 5.0)
    > LowerLeft: (10.0, 10.0)
    > UpperLeft: (10.0, 15.0)
    > UpperRight: (15.0, 15.0)
    > LowerRight: (15.0, 10.0)

debugger_visualizer 与 GDB 一起使用

GDB 支持使用结构化的 Python 脚本,称为漂亮打印机,描述类型在调试器视图中应如何可视化。有关漂亮打印机的详细信息,请参阅 GDB 的漂亮打印文档

Note

在 GDB 下调试二进制文件时,嵌入的漂亮打印机不会自动加载。

有两种启用自动加载嵌入漂亮打印机的方法:

  1. 启动 GDB 时带额外参数以显式将目录或二进制文件添加到自动加载安全路径:gdb -iex "add-auto-load-safe-path safe-path path/to/binary" path/to/binary 有关更多信息,请参阅 GDB 的自动加载文档
  2. $HOME/.config/gdb 下创建名为 gdbinit 的文件(如果目录不存在,您可能需要创建它)。将以下行添加到该文件:add-auto-load-safe-path path/to/binary

这些脚本使用 gdb_script_file 键嵌入,该键是相对于源文件的路径。

Example

#![debugger_visualizer(gdb_script_file = "printer.py")]

struct Person {
    name: String,
    age: i32,
}

fn main() {
    let bob = Person { name: String::from("Bob"), age: 10 };
    println!("set breakpoint here");
}

printer.py 包含:

import gdb

class PersonPrinter:
    "Print a Person"

    def __init__(self, val):
        self.val = val
        self.name = val["name"]
        self.age = int(val["age"])

    def to_string(self):
        return "{} is {} years old.".format(self.name, self.age)

def lookup(val):
    lookup_tag = val.type.tag
    if lookup_tag is None:
        return None
    if "foo::Person" == lookup_tag:
        return PersonPrinter(val)

    return None

gdb.current_objfile().pretty_printers.append(lookup)

当 crate 的调试可执行文件传递给 GDB1 时,print bob 将显示:

"Bob" is 10 years old.

collapse_debuginfo 属性

*collapse_debuginfo [属性]*控制在为调用此宏的代码生成调试信息时,宏定义中的代码位置是否折叠为与宏调用位置关联的单个位置。

Example

#![allow(unused)]
fn main() {
#[collapse_debuginfo(yes)]
macro_rules! example {
    () => {
        println!("hello!");
    };
}
}

使用调试器时,调用 example 宏可能看起来像是在调用函数。也就是说,当您单步执行到调用位置时,它可能显示宏调用而不是展开的代码。

collapse_debuginfo 属性的语法是:

Syntax
CollapseDebuginfoAttributecollapse_debuginfo ( CollapseDebuginfoOption )

CollapseDebuginfoOption
      yes
    | no
    | external

collapse_debuginfo 属性只能应用于 macro_rules 定义

collapse_debuginfo 属性只能在宏上使用一次。

collapse_debuginfo 属性接受这些选项:

  • #[collapse_debuginfo(yes)] — 调试信息中的代码位置被折叠。
  • #[collapse_debuginfo(no)] — 调试信息中的代码位置不被折叠。
  • #[collapse_debuginfo(external)] — 仅当宏来自不同的 crate 时,调试信息中的代码位置才被折叠。

对于没有此属性的宏,external 行为是默认的,除非它们是内置宏。对于内置宏,默认值是 yes

Note

rustc 有一个 -C collapse-macro-debuginfo CLI 选项,可以覆盖默认行为和任何 #[collapse_debuginfo] 属性的值。


  1. 注意:这假设您正在使用 rust-gdb 脚本,该脚本为标准库类型(如 String)配置了漂亮打印机。

语句和表达式

Rust 主要是一种表达式语言。这意味着大多数产生值或引起效果的求值都由统一的语法类别_表达式_指导。每种表达式通常可以_嵌套_在其他种类的表达式中,并且表达式求值的规则涉及指定表达式产生的值及其子表达式本身的求值顺序。

相比之下,语句主要用于包含和显式排序表达式求值。

语句

Syntax
Statement
      ;
    | Item
    | LetStatement
    | ExpressionStatement
    | OuterAttribute* MacroInvocationSemi

语句(Statement)是的组成部分,而块又是外部表达式函数的组成部分。

Rust 有两种语句:声明语句表达式语句

声明语句

声明语句是将一个或多个名称引入封闭语句块的语句。声明的名称可以表示新变量或新

两种声明语句是项声明和 let 语句。

项声明

项声明语句的语法形式与模块中的项声明相同。

在语句块中声明项会将其作用域限制为包含该语句的块。该项不会被赋予规范路径,其可能声明的任何子项也不会。

例外情况是,只要该项(如果适用,还有 trait)可访问,实现定义的关联项仍然可以在外部作用域中访问。否则,它在含义上与在模块内声明该项相同。

没有隐式捕获包含函数的泛型参数、参数和局部变量。例如,inner 不能访问 outer_var

#![allow(unused)]
fn main() {
fn outer() {
  let outer_var = true;

  fn inner() { /* outer_var is not in scope here */ }

  inner();
}
}

let 语句

Syntax
LetStatement
    OuterAttribute* let PatternNoTopAlt ( : Type )?
    (
          = Expression
        | = Expressionexcept LazyBooleanExpression or end with a }
              else BlockExpressionNoInnerAttributes
    )? ;

let 语句引入一组新的变量,由模式给出。模式后面可以选择性地跟类型标注,然后结束,或者后跟初始化器表达式加上可选的 else 块。

当未给出类型标注时,编译器将推断类型,或者如果类型信息不足无法进行确定性推断则发出错误信号。

变量声明引入的任何变量从声明点到封闭块作用域结束都是可见的,除非它们被另一个变量声明遮蔽。

如果不存在 else 块,则模式必须是不可反驳的。如果存在 else 块,则模式可以是可反驳的。

如果模式不匹配(这要求它是可反驳的),则执行 else 块。else 块必须始终发散(求值为永不类型)。

#![allow(unused)]
fn main() {
let (mut v, w) = (vec![1, 2, 3], 42); // The bindings may be mut or const
let Some(t) = v.pop() else { // Refutable patterns require an else block
    panic!(); // The else block must diverge
};
let [u, v] = [v[0], v[1]] else { // This pattern is irrefutable, so the compiler
                                 // will lint as the else block is redundant.
    panic!();
};
}

表达式语句

Syntax
ExpressionStatement
      ExpressionWithoutBlock ;
    | ExpressionWithBlock ;?

表达式语句是求值表达式并忽略其结果的语句。通常,表达式语句的目的是触发求值其表达式的效果。

仅由块表达式或控制流表达式组成的表达式,如果在允许语句的上下文中使用,可以省略尾部分号。这可能会在将其解析为独立语句还是另一个表达式的一部分之间产生歧义;在这种情况下,它被解析为语句。

用作语句时 ExpressionWithBlock 表达式的类型必须是单元类型。

#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
v.pop();          // Ignore the element returned from pop
if v.is_empty() {
    v.push(5);
} else {
    v.remove(0);
}                 // Semicolon can be omitted.
[1];              // Separate expression statement, not an indexing expression.
}

当省略尾部分号时,结果必须是类型 ()

#![allow(unused)]
fn main() {
// bad: the block's type is i32, not ()
// Error: expected `()` because of default return type
// if true {
//   1
// }

// good: the block's type is i32
if true {
  1
} else {
  2
};
}

语句上的属性

语句接受外部属性。在语句上有意义的属性是 cfglint 检查属性

表达式

表达式(Expression)可以有两个角色:它总是产生一个,并且可能有效果(也称为“副作用“)。

表达式求值为一个值,并在求值期间产生效果。

许多表达式包含子表达式,称为表达式的操作数

每种表达式的含义决定了几件事:

  • 求值表达式时是否求值操作数
  • 求值操作数的顺序
  • 如何组合操作数的值以获得表达式的值

通过这种方式,表达式的结构决定了执行的结构。块只是另一种表达式,因此块、语句、表达式和块可以递归地嵌套在彼此内部,达到任意深度。

Note

我们给表达式的操作数命名以便讨论,但这些名称不稳定,可能会更改。

表达式优先级

Rust 运算符和表达式的优先级按以下顺序排列,从强到弱。相同优先级的二元运算符按其关联性分组。

运算符/表达式关联性
路径
方法调用
字段表达式从左到右
函数调用数组索引
?
一元 - ! * 借用
as从左到右
* / %从左到右
+ -从左到右
<< >>从左到右
&从左到右
^从左到右
|从左到右
== != < > <= >=需要括号
&&从左到右
||从左到右
.. ..=需要括号
= += -= *= /= %=
&= |= ^= <<= >>=
从右到左
return break 闭包

操作数的求值顺序

以下表达式列表都以相同的方式求值其操作数,如列表之后所述。其他表达式要么不接受操作数,要么按其各自页面中描述的条件求值。

  • 解引用表达式
  • 错误传播表达式
  • 取反表达式
  • 算术和逻辑二元运算符
  • 比较运算符
  • 类型转换表达式
  • 分组表达式
  • 数组表达式
  • Await 表达式
  • 索引表达式
  • 元组表达式
  • 元组索引表达式
  • 结构体表达式
  • 调用表达式
  • 方法调用表达式
  • 字段表达式
  • Break 表达式
  • 范围表达式
  • Return 表达式

这些表达式的操作数在应用表达式的效果之前求值。接受多个操作数的表达式按源代码中编写的顺序从左到右求值。

Note

哪些子表达式是表达式的操作数由上一节的表达式优先级确定。

例如,两个 next 方法调用将始终以相同的顺序调用:

#![allow(unused)]
fn main() {
// Using vec instead of array to avoid references
// since there is no stable owned array iterator
// at the time this example was written.
let mut one_two = vec![1, 2].into_iter();
assert_eq!(
    (1, 2),
    (one_two.next().unwrap(), one_two.next().unwrap())
);
}

Note

由于这是递归应用的,这些表达式也从最内层到最外层求值,忽略兄弟节点直到没有内部子表达式。

位置表达式和值表达式

表达式分为两个主要类别:位置表达式和值表达式;还有第三个次要类别的表达式称为赋值表达式。在每个表达式内,操作数同样可能出现在位置上下文或值上下文中。表达式的求值取决于其自身的类别和它出现的上下文。

位置表达式是表示内存位置的表达式。

这些表达式是引用局部变量的路径静态变量解引用*expr)、数组索引表达式(expr[expr])、字段引用(expr.f)和括号位置表达式。

所有其他表达式都是值表达式。

值表达式是表示实际值的表达式。

以下是位置表达式上下文:

Note

历史上,位置表达式被称为lvalues,值表达式被称为rvalues

赋值表达式是出现在赋值表达式左操作数中的表达式。具体来说,赋值表达式是:

赋值表达式内允许任意括号。

移动和复制类型

当位置表达式在值表达式上下文中求值,或在模式中按值绑定时,它表示_在_该内存位置中持有的值。

如果该值的类型实现 Copy,则该值将被复制。

在剩余情况下,如果该类型是 Sized,则可能可以移动该值。

只有以下位置表达式可以被移出:

在从求值为局部变量的位置表达式移出后,该位置被取消初始化,在重新初始化之前无法再次读取。

在所有其他情况下,尝试在值表达式上下文中使用位置表达式是错误的。

可变性

要使位置表达式被赋值、可变借用隐式可变借用或绑定到包含 ref mut 的模式,它必须是_可变的_。我们将这些称为可变位置表达式。相比之下,其他位置表达式称为不可变位置表达式

以下表达式可以是可变位置表达式上下文:

  • 当前未借用的可变变量
  • 可变 static
  • 临时值
  • 字段:在可变位置表达式上下文中求值子表达式。
  • *mut T 指针的解引用
  • 类型为 &mut T 的变量或变量字段的解引用。注意:这是下一个规则要求的例外。
  • 实现 DerefMut 的类型的解引用:这要求被解引用的值在可变位置表达式上下文中求值。
  • 实现 IndexMut 的类型的数组索引:这在可变位置表达式上下文中求值被索引的值,但不求值索引。

临时值

在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名内存位置并初始化为该值。表达式求值为该位置,除非被提升static。临时值的丢弃作用域通常是封闭语句的末尾。

超级宏

某些内置宏可能创建临时值,其作用域可能被扩展。这些临时值是超级临时值,这些宏是超级宏。这些宏的调用超级宏调用表达式。这些宏的参数可以是超级操作数

Note

当超级宏调用表达式是扩展表达式时,其超级操作数是扩展表达式,超级临时值的作用域扩展。参见 destructors.scope.lifetime-extension.exprs

format_args!

除了格式字符串参数外,传递给 format_args! 的所有参数都是超级操作数

#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
// Due to the call being an extending expression and the argument
// being a super operand, the inner block is an extending expression,
// so the scope of the temporary created in its trailing expression
// is extended.
let _ = format_args!("{}", { &temp() }); // OK
}

format_args! 的超级操作数被隐式借用,因此是位置表达式上下文。当值表达式作为参数传递时,它会创建一个超级临时值

#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
let x = format_args!("{}", temp());
x; // <-- The temporary is extended, allowing use here.
}

调用 format_args! 的展开有时会创建其他内部超级临时值

#![allow(unused)]
fn main() {
let x = {
    // This call creates an internal temporary.
    let x = format_args!("{:?}", 0);
    x // <-- The temporary is extended, allowing its use here.
}; // <-- The temporary is dropped here.
x; // ERROR
}
#![allow(unused)]
fn main() {
// This call doesn't create an internal temporary.
let x = { let x = format_args!("{}", 0); x };
x; // OK
}

Note

format_args! 何时创建或不创建内部临时值的细节目前未指定。

pin!

pin! 的参数是超级操作数

#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// As above for `format_args!`.
let _ = pin!({ &temp() }); // OK
}

pin! 的参数是值表达式上下文,并创建一个超级临时值

#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// The argument is evaluated into a super temporary.
let x = pin!(temp());
// The temporary is extended, allowing its use here.
x; // OK
}

隐式借用

某些表达式将通过隐式借用将表达式视为位置表达式。例如,可以直接比较两个未大小的切片是否相等,因为 == 运算符隐式借用其操作数:

#![allow(unused)]
fn main() {
let c = [1, 2, 3];
let d = vec![1, 2, 3];
let a: &[i32];
let b: &[i32];
a = &c;
b = &d;
// ...
*a == *b;
// Equivalent form:
::std::cmp::PartialEq::eq(&*a, &*b);
}

可以在以下表达式中进行隐式借用:

重载 trait

以下许多运算符和表达式也可以使用 std::opsstd::cmp 中的 trait 为其他类型重载。这些 trait 也存在于 core::opscore::cmp 中,名称相同。

表达式属性

表达式之前的外部属性仅在少数特定情况下允许:

它们在以下情况之前永远不允许:

字面量表达式

Syntax
LiteralExpression
      CHAR_LITERAL
    | STRING_LITERAL
    | RAW_STRING_LITERAL
    | BYTE_LITERAL
    | BYTE_STRING_LITERAL
    | RAW_BYTE_STRING_LITERAL
    | C_STRING_LITERAL
    | RAW_C_STRING_LITERAL
    | INTEGER_LITERAL
    | FLOAT_LITERAL
    | true
    | false

_字面量表达式_是由单个词法单元(而不是词法单元序列)组成的表达式,它立即直接表示它求值到的值,而不是通过名称或其他求值规则引用它。

字面量是常量表达式的一种形式,因此(主要)在编译时求值。

前面描述的每种词法字面量形式都可以组成字面量表达式,关键字 truefalse 也可以。

#![allow(unused)]
fn main() {
"hello";   // string type
'5';       // character type
5;         // integer type
}

在下面的描述中,词法单元的_字符串表示_是输入中与 Lexer 语法片段中词法单元的产生式匹配的字符序列。

Note

此字符串表示从不包含紧跟在 U+000A (LF) 之后的字符 U+000D (CR):这对之前已被转换为单个 U+000A (LF)。

转义

下面的文本字面量表达式的描述使用了几种形式的_转义_。

每种转义形式的特征是:

  • 转义序列:以 U+005C\)开头的字符序列
  • 转义值:单个字符或空字符序列

在下面的转义定义中:

  • _八进制数字_是范围 [0-7] 中的任何字符。
  • _十六进制数字_是范围 [0-9]、[a-f] 或 [A-F] 中的任何字符。

简单转义

下表第一列中出现的每个字符序列都是转义序列。

在每种情况下,转义值是第二列相应条目中给出的字符。

转义序列转义值
\0U+0000 (NUL)
\tU+0009 (HT)
\nU+000A (LF)
\rU+000D (CR)
\"U+0022 (QUOTATION MARK)
\'U+0027 (APOSTROPHE)
\\U+005C (REVERSE SOLIDUS)

8 位转义

转义序列由 \x 后跟两个十六进制数字组成。

转义值是其 Unicode 标量值是将转义序列中的最后两个字符解释为十六进制整数的结果的字符,就像使用 u8::from_str_radix 以 16 为基数一样。

Note

因此,转义值的 Unicode 标量值u8 范围内。

7 位转义

转义序列由 \x 后跟一个八进制数字然后一个十六进制数字组成。

转义值是其 Unicode 标量值是将转义序列中的最后两个字符解释为十六进制整数的结果的字符,就像使用 u8::from_str_radix 以 16 为基数一样。

Unicode 转义

转义序列由 \u{ 后跟一系列字符(每个字符都是十六进制数字或 _),然后是 } 组成。

转义值是其 Unicode 标量值是将转义序列中包含的十六进制数字解释为十六进制整数的结果的字符,就像使用 u32::from_str_radix 以 16 为基数一样。

Note

CHAR_LITERALSTRING_LITERAL 词法单元的允许形式确保存在这样的字符。

字符串继续转义

转义序列由 \ 紧接着 U+000A (LF),以及下一个非空白字符之前的所有后续空白字符组成。为此,空白字符是 U+0009 (HT)、U+000A (LF)、U+000D (CR) 和 U+0020 (SPACE)。

转义值是空字符序列。

Note

此形式转义的效果是字符串继续跳过后续空白,包括额外的换行符。因此 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)可能令人困惑和意外。此行为将来可能会调整。在做出决定之前,建议避免依赖行继续跳过多个换行符。有关更多信息,请参阅此问题

字符字面量表达式

字符字面量表达式由单个 CHAR_LITERAL 词法单元组成。

表达式的类型是原始 char 类型。

词法单元不能有后缀。

词法单元的_字面量内容_是词法单元的字符串表示中第一个 U+0027')之后和最后一个 U+0027')之前的字符序列。

字面量表达式的_表示字符_从字面量内容派生如下:

  • 否则,表示字符是组成字面量内容的单个字符。

表达式的值是对应于表示字符的 Unicode 标量值char

Note

CHAR_LITERAL 词法单元的允许形式确保这些规则总是产生单个字符。

字符字面量表达式的示例:

#![allow(unused)]
fn main() {
'R';                               // R
'\'';                              // '
'\x52';                            // R
'\u{00E6}';                        // LATIN SMALL LETTER AE (U+00E6)
}

字符串字面量表达式

字符串字面量表达式由单个 STRING_LITERALRAW_STRING_LITERAL 词法单元组成。

表达式的类型是原始 str 类型的共享引用(具有 static 生命周期)。即,类型为 &'static str

词法单元不能有后缀。

词法单元的_字面量内容_是词法单元的字符串表示中第一个 U+0022")之后和最后一个 U+0022")之前的字符序列。

字面量表达式的_表示字符串_是从字面量内容派生的字符序列,如下所示:

  • 如果词法单元是 RAW_STRING_LITERAL,则表示字符串与字面量内容相同。

表达式的值是对静态分配的 str 的引用,包含表示字符串的 UTF-8 编码。

字符串字面量表达式的示例:

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

字节字面量表达式

字节字面量表达式由单个 BYTE_LITERAL 词法单元组成。

表达式的类型是原始 u8 类型。

词法单元不能有后缀。

词法单元的_字面量内容_是词法单元的字符串表示中第一个 U+0027')之后和最后一个 U+0027')之前的字符序列。

字面量表达式的_表示字符_从字面量内容派生如下:

  • 如果字面量内容是以下形式的转义序列之一,则表示字符是转义序列的转义值:
  • 否则,表示字符是组成字面量内容的单个字符。

表达式的值是表示字符的 Unicode 标量值

Note

BYTE_LITERAL 词法单元的允许形式确保这些规则总是产生单个字符,其 Unicode 标量值在 u8 范围内。

字节字面量表达式的示例:

#![allow(unused)]
fn main() {
b'R';                              // 82
b'\'';                             // 39
b'\x52';                           // 82
b'\xA0';                           // 160
}

字节字符串字面量表达式

字节字符串字面量表达式由单个 BYTE_STRING_LITERALRAW_BYTE_STRING_LITERAL 词法单元组成。

表达式的类型是元素类型为 u8 的数组的共享引用(具有 static 生命周期)。即,类型为 &'static [u8; N],其中 N 是下面描述的表示字符串中的字节数。

词法单元不能有后缀。

词法单元的_字面量内容_是词法单元的字符串表示中第一个 U+0022")之后和最后一个 U+0022")之前的字符序列。

字面量表达式的_表示字符串_是从字面量内容派生的字符序列,如下所示:

表达式的值是对静态分配的数组的引用,包含表示字符串中字符的 Unicode 标量值,按相同顺序。

Note

BYTE_STRING_LITERALRAW_BYTE_STRING_LITERAL 词法单元的允许形式确保这些规则总是产生在 u8 范围内的数组元素值。

字节字符串字面量表达式的示例:

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

C 字符串字面量表达式

C 字符串字面量表达式由单个 C_STRING_LITERALRAW_C_STRING_LITERAL 词法单元组成。

表达式的类型是标准库 CStr 类型的共享引用(具有 static 生命周期)。即,类型为 &'static core::ffi::CStr

词法单元不能有后缀。

词法单元的_字面量内容_是词法单元的字符串表示中第一个 " 之后和最后一个 " 之前的字符序列。

字面量表达式的_表示字节_是从字面量内容派生的字节序列,如下所示:

Note

C_STRING_LITERALRAW_C_STRING_LITERAL 词法单元的允许形式确保表示字节从不包含空字节。

表达式的值是对静态分配的 CStr 的引用,其字节数组包含表示字节后跟空字节。

C 字符串字面量表达式的示例:

#![allow(unused)]
fn main() {
c"foo"; cr"foo";                     // foo
c"\"foo\""; cr#""foo""#;             // "foo"

c"foo #\"# bar";
cr##"foo #"# bar"##;                 // foo #"# bar

c"\x52"; c"R"; cr"R";                // R
c"\\x52"; cr"\x52";                  // \x52

c"æ";                                // LATIN SMALL LETTER AE (U+00E6)
c"\u{00E6}";                         // LATIN SMALL LETTER AE (U+00E6)
c"\xC3\xA6";                         // LATIN SMALL LETTER AE (U+00E6)

c"\xE6".to_bytes();                  // [230]
c"\u{00E6}".to_bytes();              // [195, 166]
}

整数字面量表达式

整数字面量表达式由单个 INTEGER_LITERAL 词法单元组成。

如果词法单元有后缀,后缀必须是原始整数类型之一的名称:u8i8u16i16u32i32u64i64u128i128usizeisize,表达式具有该类型。

如果词法单元没有后缀,表达式的类型由类型推断确定:

  • 如果可以从周围的程序上下文中_唯一_确定整数类型,则表达式具有该类型。
  • 如果程序上下文对类型约束不足,则默认为有符号 32 位整数 i32
  • 如果程序上下文对类型过度约束,则被视为静态类型错误。

整数字面量表达式的示例:

#![allow(unused)]
fn main() {
123;                               // type i32
123i32;                            // type i32
123u32;                            // type u32
123_u32;                           // type u32
let a: u64 = 123;                  // type u64

0xff;                              // type i32
0xff_u8;                           // type u8

0o70;                              // type i32
0o70_i16;                          // type i16

0b1111_1111_1001_0000;             // type i32
0b1111_1111_1001_0000i64;          // type i64

0usize;                            // type usize
}

表达式的值从词法单元的字符串表示确定如下:

  • 通过检查字符串的前两个字符来选择整数基数,如下所示:

    • 0b 表示基数 2
    • 0o 表示基数 8
    • 0x 表示基数 16
    • 否则基数为 10。
  • 如果基数不是 10,则从字符串中删除前两个字符。
  • 从字符串中删除任何后缀。
  • 从字符串中删除任何下划线。
  • 字符串被转换为 u128 值,就像使用 u128::from_str_radix 以选定基数一样。如果值不适合 u128,则是编译器错误。

Note

如果字面量不适合表达式的类型,最终的类型转换将截断字面量的值。rustc 包含一个名为 overflowing_literalslint 检查,默认为 deny,会拒绝发生这种情况的表达式。

Note

例如,-1i8取反运算符对字面量表达式 1i8 的应用,而不是单个整数字面量表达式。有关表示有符号类型的最负值的说明,请参阅溢出

浮点数字面量表达式

浮点数字面量表达式有两种形式之一:

如果词法单元有后缀,后缀必须是原始浮点类型之一的名称:f32f64,表达式具有该类型。

如果词法单元没有后缀,表达式的类型由类型推断确定:

  • 如果可以从周围的程序上下文中_唯一_确定浮点类型,则表达式具有该类型。
  • 如果程序上下文对类型约束不足,则默认为 f64
  • 如果程序上下文对类型过度约束,则被视为静态类型错误。

浮点数字面量表达式的示例:

#![allow(unused)]
fn main() {
123.0f64;        // type f64
0.1f64;          // type f64
0.1f32;          // type f32
12E+99_f64;      // type f64
5f32;            // type f32
let x: f64 = 2.; // type f64
}

表达式的值从词法单元的字符串表示确定如下:

  • 从字符串中删除任何后缀。
  • 从字符串中删除任何下划线。

Note

例如,-1.0取反运算符对字面量表达式 1.0 的应用,而不是单个浮点数字面量表达式。

Note

infNaN 不是字面量词法单元。可以使用 f32::INFINITYf64::INFINITYf32::NANf64::NAN 常量代替字面量表达式。在 rustc 中,足够大以至于被求值为无穷大的字面量将触发 overflowing_literals lint 检查。

布尔字面量表达式

布尔字面量表达式由关键字 truefalse 之一组成。

表达式的类型是原始布尔类型,其值为:

  • 如果关键字是 true 则为 true
  • 如果关键字是 false 则为 false

路径表达式

Syntax
PathExpression
      PathInExpression
    | QualifiedPathInExpression

用作表达式上下文的路径表示局部变量或项。

解析为局部变量或静态变量的路径表达式是位置表达式;其他路径是值表达式

使用 static mut 变量需要 [unsafe 块]。

#![allow(unused)]
fn main() {
mod globals {
    pub static STATIC_VAR: i32 = 5;
    pub static mut STATIC_MUT_VAR: i32 = 7;
}
let local_var = 3;
local_var;
globals::STATIC_VAR;
unsafe { globals::STATIC_MUT_VAR };
let some_constructor = Some::<i32>;
let push_integer = Vec::<i32>::push;
let slice_reverse = <[i32]>::reverse;
}

关联常量的求值方式与 [const 块]相同。

块表达式

Syntax
BlockExpression
    {
        InnerAttribute*
        Statements?
    }

BlockExpressionNoInnerAttributes
    {
        Statements?
    }

Statements
      Statement+
    | Statement+ ExpressionWithoutBlock
    | ExpressionWithoutBlock

块表达式是控制流表达式,也是项和变量声明的匿名命名空间作用域。

作为控制流表达式,块按顺序执行其组成的非项声明语句,然后执行其最后的可选表达式。

作为匿名命名空间作用域,项声明仅在块本身内有效,由 let 语句声明的变量从下一个语句到块结束都有效。有关更多详细信息,请参阅作用域章节。

块的语法是 {,然后是任何内部属性,然后是任意数量的语句,然后是可选的表达式(称为最终操作数),最后是 }

语句通常需要后跟分号,但有两个例外:

  1. 项声明语句不需要后跟分号。
  2. 表达式语句通常需要后跟分号,除非其外部表达式是流控制表达式。

此外,语句之间允许使用额外的分号,但这些分号不影响语义。

求值块表达式时,除项声明语句外,每个语句都按顺序执行。

然后执行最终操作数(如果给出)。

当块包含最终操作数时,块具有该最终操作数的类型和值。

#![allow(unused)]
fn main() {
let x: u8 = { 0u8 }; // `0u8` is the final operand.
assert_eq!(x, 0);
let x: u8 = { (); 0u8 }; // As above.
assert_eq!(x, 0);
}

当块不包含最终操作数且块不发散时,块具有单元类型单元值

#![allow(unused)]
fn main() {
let x: () = {}; // Has no final operand.
assert_eq!(x, ());
let x: () = { 0u8; }; // As above.
assert_eq!(x, ());
}

当块不包含最终操作数且块发散时,块具有永不类型且没有最终值(因为其类型是无人居住的)。

#![allow(unused)]
fn main() {
fn f() -> ! { loop {}; } // Diverges and has no final operand.
//          ^^^^^^^^^^^^
// The body of a function is a block expression.
}

Note

请注意,没有最终操作数的块与具有显式单元类型最终操作数的块是不同的。例如,即使此块发散,块的类型也是单元而不是永不

#![allow(unused)]
fn main() {
fn f() -> ! { loop {}; () } // ERROR: Mismatched types.
//          ^^^^^^^^^^^^^^^ This block has unit type.
}

Note

作为控制流表达式,如果块表达式是表达式语句的外部表达式,则预期类型为 (),除非它后面紧跟分号。

如果所有可达的控制流路径都包含发散表达式,则块被认为是发散的,除非该表达式是未被读取的位置表达式

#![allow(unused)]
fn main() {
#![ feature(never_type) ]
fn no_control_flow() -> ! {
    // There are no conditional statements, so this entire function body is diverging.
    loop {}
}

fn control_flow_diverging() -> ! {
    // All paths are diverging, so this entire function body is diverging.
    if true {
        loop {}
    } else {
        loop {}
    }
}

fn control_flow_not_diverging() -> () {
    // Some paths are not diverging, so this entire block is not diverging.
    if true {
        ()
    } else {
        loop {}
    }
}

// Note: This makes use of the unstable never type which is only available on
// Rust's nightly channel. This is done for illustration purposes. It is
// possible to encounter this scenario in stable Rust, but requires a more
// convoluted example.
struct Foo {
    x: !,
}

fn make<T>() -> T { loop {} }

fn diverging_place_read() -> ! {
    let foo = Foo { x: make() };
    // A read of a place expression produces a diverging block.
    let _x = foo.x;
}
}
#![allow(unused)]
fn main() {
#![ feature(never_type) ]
fn make<T>() -> T { loop {} }
struct Foo {
    x: !,
}
fn diverging_place_not_read() -> ! {
    let foo = Foo { x: make() };
    // Assignment to `_` means the place is not read.
    let _ = foo.x;
} // ERROR: Mismatched types.
}

块始终是值表达式,在值表达式上下文中求值最后一个操作数。

Note

如果确实需要,这可用于强制移动值。例如,以下示例在调用 consume_self 时失败,因为结构体在块表达式中从 s 移出。

#![allow(unused)]
fn main() {
struct Struct;

impl Struct {
    fn consume_self(self) {}
    fn borrow_self(&self) {}
}

fn move_by_block_expression() {
    let s = Struct;

    // Move the value out of `s` in the block expression.
    (&{ s }).borrow_self();

    // Fails to execute because `s` is moved out of.
    s.consume_self();
}
}

async

Syntax
AsyncBlockExpressionasync move? BlockExpression

async 块是块表达式的变体,它求值为一个 future。

块的最终表达式(如果存在)确定 future 的结果值。

执行 async 块类似于执行闭包表达式:其直接效果是产生并返回一个匿名类型。

闭包返回实现一个或多个 std::ops::Fn trait 的类型,而 async 块返回的类型实现 std::future::Future trait。

此类型的实际数据格式未指定。

Note

rustc 生成的 future 类型大致等同于每个 await 点一个变体的枚举,其中每个变体存储从其对应点恢复所需的数据。

2018 Edition differences

Async 块仅从 Rust 2018 版本开始可用。

捕获模式

Async 块使用与闭包相同的捕获模式从其环境捕获变量。与闭包一样,当编写 async { .. } 时,每个变量的捕获模式将从块的内容推断。然而,async move { .. } 块将把所有引用的变量移入结果 future。

异步上下文

因为 async 块构造 future,它们定义了一个异步上下文,该上下文反过来可以包含 await 表达式。异步上下文由 async 块以及异步函数的主体建立,其语义根据 async 块定义。

控制流运算符

Async 块的作用类似于函数边界,很像闭包。

因此,? 运算符和 return 表达式都影响 future 的输出,而不是封闭函数或其他上下文。也就是说,从 async 块内 return <expr> 将返回 <expr> 的结果作为 future 的输出。类似地,如果 <expr>? 传播错误,则该错误作为 future 的结果传播。

最后,breakcontinue 关键字不能用于从 async 块分支出去。因此以下代码是非法的:

#![allow(unused)]
fn main() {
loop {
    async move {
        break; // error[E0267]: `break` inside of an `async` block
    }
}
}

const

Syntax
ConstBlockExpressionconst BlockExpression

const 块是块表达式的变体,其主体在编译时而不是运行时求值。

Const 块允许您定义常量值而无需定义新的常量项,因此它们有时也称为内联 const。它还支持类型推断,因此无需指定类型,与常量项不同。

Const 块能够引用作用域中的泛型参数,与自由常量项不同。它们被脱糖为具有作用域中泛型参数的常量项(类似于关联常量,但没有它们关联的 trait 或类型)。例如,此代码:

#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
    const { std::mem::size_of::<T>() + 1 }
}
}

等同于:

#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
    {
        struct Const<T>(T);
        impl<T> Const<T> {
            const CONST: usize = std::mem::size_of::<T>() + 1;
        }
        Const::<T>::CONST
    }
}
}

如果 const 块表达式在运行时执行,则保证求值常量,即使其返回值被忽略:

#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
    // If this code ever gets executed, then the assertion has definitely
    // been evaluated at compile-time.
    const { assert!(std::mem::size_of::<T>() > 0); }
    // Here we can have unsafe code relying on the type being non-zero-sized.
    /* ... */
    42
}
}

如果 const 块表达式未在运行时执行,则可能求值也可能不求值:

#![allow(unused)]
fn main() {
if false {
    // The panic may or may not occur when the program is built.
    const { panic!(); }
}
}

unsafe

Syntax
UnsafeBlockExpressionunsafe BlockExpression

有关何时使用 unsafe 的更多信息,请参阅 [unsafe 块]。

代码块可以用 unsafe 关键字前缀,以允许不安全操作。示例:

#![allow(unused)]
fn main() {
unsafe {
    let b = [13u8, 17u8];
    let a = &b[0] as *const u8;
    assert_eq!(*a, 13);
    assert_eq!(*a.offset(1), 17);
}

unsafe fn an_unsafe_fn() -> i32 { 10 }
let a = unsafe { an_unsafe_fn() };
}

标记块表达式

标记块表达式在循环和其他可中断表达式部分中记录。

块表达式上的属性

在以下情况下,块表达式的开花括号之后直接允许使用内部属性

在块表达式上有意义的属性是 cfglint 检查属性

例如,此函数在 unix 平台上返回 true,在其他平台上返回 false

#![allow(unused)]
fn main() {
fn is_unix_platform() -> bool {
    #[cfg(unix)] { true }
    #[cfg(not(unix))] { false }
}
}

运算符表达式

运算符由 Rust 语言为内置类型定义。

以下许多运算符也可以使用 std::opsstd::cmp 中的 trait 进行重载。

溢出

在调试模式下编译时,整数运算符在溢出时会 panic。-C debug-assertions-C overflow-checks 编译器标志可用于更直接地控制此行为。以下情况被视为溢出:

  • +* 或二元 - 创建的值大于最大值或小于可存储的最小值时。
  • 对任何有符号整数类型的最负值应用一元 -,除非操作数是字面量表达式(或单独在一个或多个分组表达式内的字面量表达式)。
  • 使用 /%,其中左参数是有符号整数类型的最小整数,右参数是 -1。出于遗留原因,即使禁用了 -C overflow-checks,也会发生这些检查。
  • 使用 <<>>,其中右参数大于或等于左参数类型中的位数,或者是负数。

Note

一元 - 后面的字面量表达式的例外意味着 -128_i8let j: i8 = -(128) 等形式永远不会导致 panic,并且具有预期值 -128。

在这些情况下,字面量表达式已经具有其类型的最负值(例如,128_i8 的值为 -128),因为整数字面量按整数字面量表达式中的描述截断为其类型。

由于二进制补码溢出约定,对这些最负值的取反使值保持不变。

rustc 中,这些最负表达式也被 overflowing_literals lint 检查忽略。

借用运算符

Syntax
BorrowExpression
      ( & | && ) Expression
    | ( & | && ) mut Expression
    | ( & | && ) raw const Expression
    | ( & | && ) raw mut Expression

&(共享借用)和 &mut(可变借用)运算符是一元前缀运算符。

当应用于位置表达式时,此表达式产生指向值所引用位置的引用(指针)。

在引用期间,内存位置也被置于借用状态。对于共享借用(&),这意味着该位置不能被修改,但可以被读取或再次共享。对于可变借用(&mut),在借用到期之前不能以任何方式访问该位置。

&mut 在可变位置表达式上下文中求值其操作数。

如果 &&mut 运算符应用于值表达式,则会创建临时值

这些运算符不能被重载。

#![allow(unused)]
fn main() {
{
    // a temporary with value 7 is created that lasts for this scope.
    let shared_reference = &7;
}
let mut array = [-2, 3, 9];
{
    // Mutably borrows `array` for this scope.
    // `array` may only be used through `mutable_reference`.
    let mutable_reference = &mut array;
}
}

即使 && 是单个词法单元(惰性 ‘and’ 运算符),当在借用表达式上下文中使用时,它作为两个借用工作:

#![allow(unused)]
fn main() {
// same meanings:
let a = &&  10;
let a = & & 10;

// same meanings:
let a = &&&&  mut 10;
let a = && && mut 10;
let a = & & & & mut 10;
}

原始借用运算符

&raw const&raw mut原始借用运算符

这些运算符的操作数表达式在位置表达式上下文中求值。

&raw const expr 然后创建指向给定位置的类型为 *const T 的 const 原始指针,&raw mut expr 创建类型为 *mut T 的可变原始指针。

当位置表达式可能求值为未正确对齐或未存储由其类型确定的有效值的位置时,或者当创建引用会引入不正确的别名假设时,必须使用原始借用运算符而不是借用运算符。在这些情况下,使用借用运算符会通过创建无效引用导致未定义行为,但仍然可以构造原始指针。

以下是通过 packed 结构体创建指向未对齐位置的原始指针的示例:

#![allow(unused)]
fn main() {
#[repr(packed)]
struct Packed {
    f1: u8,
    f2: u16,
}

let packed = Packed { f1: 1, f2: 2 };
// `&packed.f2` would create an unaligned reference, and thus be undefined behavior!
let raw_f2 = &raw const packed.f2;
assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
}

以下是创建指向不包含有效值的位置的原始指针的示例:

#![allow(unused)]
fn main() {
use std::mem::MaybeUninit;

struct Demo {
    field: bool,
}

let mut uninit = MaybeUninit::<Demo>::uninit();
// `&uninit.as_mut().field` would create a reference to an uninitialized `bool`,
// and thus be undefined behavior!
let f1_ptr = unsafe { &raw mut (*uninit.as_mut_ptr()).field };
unsafe { f1_ptr.write(true); }
let init = unsafe { uninit.assume_init() };
}

解引用运算符

Syntax
DereferenceExpression* Expression

*(解引用)运算符也是一元前缀运算符。

当应用于指针Box 时,它表示指向的位置。

如果表达式类型为 &mut T*mut TBox<T>,并且是局部变量、局部变量的(嵌套)字段或可变位置表达式,则可以对结果内存位置进行赋值。

当应用于 Box 时,结果位置可以被移出

解引用原始指针需要 unsafe

在非指针类型上,*x不可变位置表达式上下文中等同于 *std::ops::Deref::deref(&x),在可变位置表达式上下文中等同于 *std::ops::DerefMut::deref_mut(&mut x),但当 *x 经历临时生命周期扩展时,解引用的表达式 x临时作用域也会被扩展。

#![allow(unused)]
fn main() {
struct NoCopy;
let a = &7;
assert_eq!(*a, 7);
let b = &mut 9;
*b = 11;
assert_eq!(*b, 11);
let c = Box::new(NoCopy);
let d: NoCopy = *c;
}
#![allow(unused)]
fn main() {
// The temporary holding the result of `String::new()` is extended
// to live to the end of the block, so `x` may be used in subsequent
// statements.
let x = &*String::new();
x;
}
#![allow(unused)]
fn main() {
// The temporary holding the result of `String::new()` is dropped at
// the end of the statement, so it's an error to use `y` after.
let y = &*std::ops::Deref::deref(&String::new()); // ERROR
y;
}

Try 传播表达式

Syntax
TryPropagationExpressionExpression ?

Try 传播表达式使用内部表达式的值和 Try trait 来决定是产生值(如果是,产生什么值)还是返回值给调用者(如果是,返回什么值)。

Example

#![allow(unused)]
fn main() {
use std::num::ParseIntError;
fn try_to_parse() -> Result<i32, ParseIntError> {
    let x: i32 = "123".parse()?; // `x` is `123`.
    let y: i32 = "24a".parse()?; // Returns an `Err()` immediately.
    Ok(x + y)                    // Doesn't run.
}

let res = try_to_parse();
println!("{res:?}");
assert!(res.is_err())
}
#![allow(unused)]
fn main() {
fn try_option_some() -> Option<u8> {
    let val = Some(1)?;
    Some(val)
}
assert_eq!(try_option_some(), Some(1));

fn try_option_none() -> Option<u8> {
    let val = None?;
    Some(val)
}
assert_eq!(try_option_none(), None);
}
use std::ops::ControlFlow;

pub struct TreeNode<T> {
    value: T,
    left: Option<Box<TreeNode<T>>>,
    right: Option<Box<TreeNode<T>>>,
}

impl<T> TreeNode<T> {
    pub fn traverse_inorder<B>(&self, f: &mut impl FnMut(&T) -> ControlFlow<B>) -> ControlFlow<B> {
        if let Some(left) = &self.left {
            left.traverse_inorder(f)?;
        }
        f(&self.value)?;
        if let Some(right) = &self.right {
            right.traverse_inorder(f)?;
        }
        ControlFlow::Continue(())
    }
}

fn main() {
    let n = TreeNode {
        value: 1,
        left: Some(Box::new(TreeNode{value: 2, left: None, right: None})),
        right: None,
    };
    let v = n.traverse_inorder(&mut |t| {
        if *t == 2 {
            ControlFlow::Break("found")
        } else {
            ControlFlow::Continue(())
        }
    });
    assert_eq!(v, ControlFlow::Break("found"));
}

Note

Try trait 目前不稳定,因此不能为用户类型实现。

Try 传播表达式目前大致等同于:

#![allow(unused)]
fn main() {
#![ feature(try_trait_v2) ]
fn example() -> Result<(), ()> {
let expr = Ok(());
match core::ops::Try::branch(expr) {
    core::ops::ControlFlow::Continue(val) => val,
    core::ops::ControlFlow::Break(residual) =>
        return core::ops::FromResidual::from_residual(residual),
}
Ok(())
}
}

Note

Try 传播运算符有时称为问号运算符? 运算符try 运算符

Try 传播运算符可应用于以下类型的表达式:

  • Result<T, E>
    • Result::Ok(val) 求值为 val
    • Result::Err(e) 返回 Result::Err(From::from(e))
  • Option<T>
    • Option::Some(val) 求值为 val
    • Option::None 返回 Option::None
  • ControlFlow<B, C>
    • ControlFlow::Continue(c) 求值为 c
    • ControlFlow::Break(b) 返回 ControlFlow::Break(b)
  • Poll<Result<T, E>>
    • Poll::Ready(Ok(val)) 求值为 Poll::Ready(val)
    • Poll::Ready(Err(e)) 返回 Poll::Ready(Err(From::from(e)))
    • Poll::Pending 求值为 Poll::Pending
  • Poll<Option<Result<T, E>>>
    • Poll::Ready(Some(Ok(val))) 求值为 Poll::Ready(Some(val))
    • Poll::Ready(Some(Err(e))) 返回 Poll::Ready(Some(Err(From::from(e))))
    • Poll::Ready(None) 求值为 Poll::Ready(None)
    • Poll::Pending 求值为 Poll::Pending

取反运算符

Syntax
NegationExpression
      - Expression
    | ! Expression

这是最后两个一元运算符。

此表总结了它们在原始类型上的行为以及用于为其他类型重载这些运算符的 trait。请记住,有符号整数始终使用二进制补码表示。所有这些运算符的操作数都在值表达式上下文中求值,因此被移动或复制。

符号整数bool浮点数重载 Trait
-取反*取反std::ops::Neg
!按位 NOT逻辑 NOTstd::ops::Not

* 仅适用于有符号整数类型。

以下是这些运算符的一些示例

#![allow(unused)]
fn main() {
let x = 6;
assert_eq!(-x, -6);
assert_eq!(!x, -7);
assert_eq!(true, !false);
}

算术和逻辑二元运算符

Syntax
ArithmeticOrLogicalExpression
      Expression + Expression
    | Expression - Expression
    | Expression * Expression
    | Expression / Expression
    | Expression % Expression
    | Expression & Expression
    | Expression | Expression
    | Expression ^ Expression
    | Expression << Expression
    | Expression >> Expression

二元运算符表达式都使用中缀表示法编写。

此表总结了算术和逻辑二元运算符在原始类型上的行为以及用于为其他类型重载这些运算符的 trait。请记住,有符号整数始终使用二进制补码表示。所有这些运算符的操作数都在值表达式上下文中求值,因此被移动或复制。

符号整数bool浮点数重载 Trait重载复合赋值 Trait
+加法加法std::ops::Addstd::ops::AddAssign
-减法减法std::ops::Substd::ops::SubAssign
*乘法乘法std::ops::Mulstd::ops::MulAssign
/除法*†除法std::ops::Divstd::ops::DivAssign
%取余**†取余std::ops::Remstd::ops::RemAssign
&按位 AND逻辑 ANDstd::ops::BitAndstd::ops::BitAndAssign
|按位 OR逻辑 ORstd::ops::BitOrstd::ops::BitOrAssign
^按位 XOR逻辑 XORstd::ops::BitXorstd::ops::BitXorAssign
<<左移std::ops::Shlstd::ops::ShlAssign
>>右移***std::ops::Shrstd::ops::ShrAssign

* 整数除法向零舍入。

** Rust 使用截断除法定义的取余。给定 remainder = dividend % divisor,余数将与被除数具有相同的符号。

*** 有符号整数类型上的算术右移,无符号整数类型上的逻辑右移。

† 对于整数类型,除以零会 panic。

以下是使用这些运算符的示例。

#![allow(unused)]
fn main() {
assert_eq!(3 + 6, 9);
assert_eq!(5.5 - 1.25, 4.25);
assert_eq!(-5 * 14, -70);
assert_eq!(14 / 3, 4);
assert_eq!(100 % 7, 2);
assert_eq!(0b1010 & 0b1100, 0b1000);
assert_eq!(0b1010 | 0b1100, 0b1110);
assert_eq!(0b1010 ^ 0b1100, 0b110);
assert_eq!(13 << 3, 104);
assert_eq!(-10 >> 2, -3);
}

比较运算符

Syntax
ComparisonExpression
      Expression == Expression
    | Expression != Expression
    | Expression > Expression
    | Expression < Expression
    | Expression >= Expression
    | Expression <= Expression

比较运算符也为原始类型和标准库中的许多类型定义。

链接比较运算符时需要括号。例如,表达式 a == b == c 无效,可以写成 (a == b) == c

与算术和逻辑运算符不同,用于重载这些运算符的 trait 更普遍地用于显示如何比较类型,并且可能被使用这些 trait 作为约束的函数假定为定义实际比较。标准库中的许多函数和宏可以使用该假设(尽管不是为了确保安全性)。

与上面的算术和逻辑运算符不同,这些运算符隐式地对其操作数进行共享借用,在位置表达式上下文中求值它们:

#![allow(unused)]
fn main() {
let a = 1;
let b = 1;
a == b;
// is equivalent to
::std::cmp::PartialEq::eq(&a, &b);
}

这意味着不必移出操作数。

符号含义重载方法
==等于std::cmp::PartialEq::eq
!=不等于std::cmp::PartialEq::ne
>大于std::cmp::PartialOrd::gt
<小于std::cmp::PartialOrd::lt
>=大于或等于std::cmp::PartialOrd::ge
<=小于或等于std::cmp::PartialOrd::le

以下是使用比较运算符的示例。

#![allow(unused)]
fn main() {
assert!(123 == 123);
assert!(23 != -12);
assert!(12.5 > 12.2);
assert!([1, 2, 3] < [1, 3, 4]);
assert!('A' <= 'B');
assert!("World" >= "Hello");
}

惰性布尔运算符

Syntax
LazyBooleanExpression
      Expression || Expression
    | Expression && Expression

运算符 ||&& 可以应用于布尔类型的操作数。|| 运算符表示逻辑“或“,&& 运算符表示逻辑“与“。

它们与 |& 的不同之处在于,只有当左操作数尚未确定表达式的结果时,才会求值右操作数。也就是说,|| 仅在左操作数求值为 false 时才求值其右操作数,&& 仅在求值为 true 时才求值。

#![allow(unused)]
fn main() {
let x = false || true; // true
let y = false && panic!(); // false, doesn't evaluate `panic!()`
}

类型转换表达式

Syntax
TypeCastExpressionExpression as TypeNoBounds

类型转换表达式用二元运算符 as 表示。

执行 as 表达式将左侧的值转换为右侧的类型。

as 表达式的示例:

#![allow(unused)]
fn main() {
fn sum(values: &[f64]) -> f64 { 0.0 }
fn len(values: &[f64]) -> i32 { 0 }
fn average(values: &[f64]) -> f64 {
    let sum: f64 = sum(values);
    let size: f64 = len(values) as f64;
    sum / size
}
}

as 可用于显式执行强制转换,以及以下附加转换。任何不符合强制转换规则或表中条目的转换都是编译器错误。这里 *T 表示 *const T*mut Tm 表示引用类型中的可选 mut 和指针类型中的 mutconst

e 的类型Ue as U 执行的转换
整数或浮点类型整数或浮点类型数值转换
枚举整数类型枚举转换
boolchar整数类型原始到整数转换
u8charu8char 转换
*T*V(当兼容时)指针到指针转换
*T(其中 T: Sized整数类型指针到地址转换
整数类型*V(其中 V: Sized地址到指针转换
&m₁ [T; n]*m₂ T 1数组到指针转换
*m₁ [T; n]*m₂ T 1数组到指针转换
函数项函数指针函数项到函数指针转换
函数项*V(其中 V: Sized函数项到指针转换
函数项整数函数项到地址转换
函数指针*V(其中 V: Sized函数指针到指针转换
函数指针整数函数指针到地址转换
闭包 2函数指针闭包到函数指针转换

语义

数值转换

  • 在相同大小的两个整数之间转换(例如 i32 -> u32)是无操作(Rust 对固定整数的负值使用 2 的补码)

    #![allow(unused)]
    fn main() {
    assert_eq!(42i8 as u8, 42u8);
    assert_eq!(-1i8 as u8, 255u8);
    assert_eq!(255u8 as i8, -1i8);
    assert_eq!(-1i16 as u16, 65535u16);
    }
  • 从较大整数转换为较小整数(例如 u32 -> u8)将截断

    #![allow(unused)]
    fn main() {
    assert_eq!(42u16 as u8, 42u8);
    assert_eq!(1234u16 as u8, 210u8);
    assert_eq!(0xabcdu16 as u8, 0xcdu8);
    
    assert_eq!(-42i16 as i8, -42i8);
    assert_eq!(1234u16 as i8, -46i8);
    assert_eq!(0xabcdi32 as i8, -51i8);
    }
  • 从较小整数转换为较大整数(例如 u8 -> u32)将

    • 如果源是无符号的,则零扩展
    • 如果源是有符号的,则符号扩展
    #![allow(unused)]
    fn main() {
    assert_eq!(42i8 as i16, 42i16);
    assert_eq!(-17i8 as i16, -17i16);
    assert_eq!(0b1000_1010u8 as u16, 0b0000_0000_1000_1010u16, "Zero-extend");
    assert_eq!(0b0000_1010i8 as i16, 0b0000_0000_0000_1010i16, "Sign-extend 0");
    assert_eq!(0b1000_1010u8 as i8 as i16, 0b1111_1111_1000_1010u16 as i16, "Sign-extend 1");
    }
  • 从浮点数转换为整数将向零舍入浮点数

    • NaN 将返回 0
    • 大于最大整数值的值(包括 INFINITY)将饱和到整数类型的最大值。
    • 小于最小整数值的值(包括 NEG_INFINITY)将饱和到整数类型的最小值。
    #![allow(unused)]
    fn main() {
    assert_eq!(42.9f32 as i32, 42);
    assert_eq!(-42.9f32 as i32, -42);
    assert_eq!(42_000_000f32 as i32, 42_000_000);
    assert_eq!(std::f32::NAN as i32, 0);
    assert_eq!(1_000_000_000_000_000f32 as i32, 0x7fffffffi32);
    assert_eq!(std::f32::NEG_INFINITY as i32, -0x80000000i32);
    }
  • 从整数转换为浮点数将产生最接近的可能浮点数 *

    • 如果需要,舍入根据 roundTiesToEven 模式进行 ***
    • 溢出时,产生无穷大(与输入相同的符号)
    • 注意:使用当前的数值类型集,溢出只能在 u128 as f32 对于大于或等于 f32::MAX + (0.5 ULP) 的值时发生
    #![allow(unused)]
    fn main() {
    assert_eq!(1337i32 as f32, 1337f32);
    assert_eq!(123_456_789i32 as f32, 123_456_790f32, "Rounded");
    assert_eq!(0xffffffff_ffffffff_ffffffff_ffffffff_u128 as f32, std::f32::INFINITY);
    }
  • 从 f32 转换为 f64 是完美且无损的

    #![allow(unused)]
    fn main() {
    assert_eq!(1_234.5f32 as f64, 1_234.5f64);
    assert_eq!(std::f32::INFINITY as f64, std::f64::INFINITY);
    assert!((std::f32::NAN as f64).is_nan());
    }
  • 从 f64 转换为 f32 将产生最接近的可能 f32 **

    • 如果需要,舍入根据 roundTiesToEven 模式进行 ***
    • 溢出时,产生无穷大(与输入相同的符号)
    #![allow(unused)]
    fn main() {
    assert_eq!(1_234.5f64 as f32, 1_234.5f32);
    assert_eq!(1_234_567_891.123f64 as f32, 1_234_567_890f32, "Rounded");
    assert_eq!(std::f64::INFINITY as f32, std::f32::INFINITY);
    assert!((std::f64::NAN as f32).is_nan());
    }

* 如果硬件不原生支持具有此舍入模式和溢出行为的整数到浮点转换,这些转换可能会比预期慢。

** 如果硬件不原生支持具有此舍入模式和溢出行为的 f64 到 f32 转换,这些转换可能会比预期慢。

*** 如 IEEE 754-2008 §4.3.1 中定义:选择最接近的浮点数,如果恰好在两个浮点数之间,则优先选择最低有效位为偶数的那个。

枚举转换

将枚举转换为其判别值,然后在需要时使用数值转换。转换仅限于以下类型的枚举:

#![allow(unused)]
fn main() {
enum Enum { A, B, C }
assert_eq!(Enum::A as i32, 0);
assert_eq!(Enum::B as i32, 1);
assert_eq!(Enum::C as i32, 2);
}

如果枚举实现 Drop,则不允许转换。

原始到整数转换

  • false 转换为 0true 转换为 1
  • char 转换为代码点的值,然后在需要时使用数值转换。
#![allow(unused)]
fn main() {
assert_eq!(false as i32, 0);
assert_eq!(true as i32, 1);
assert_eq!('A' as i32, 65);
assert_eq!('Ö' as i32, 214);
}

u8char 转换

转换为具有相应代码点的 char

#![allow(unused)]
fn main() {
assert_eq!(65u8 as char, 'A');
assert_eq!(214u8 as char, 'Ö');
}

指针到地址转换

从原始指针转换为整数会产生引用内存的机器地址。如果整数类型小于指针类型,地址可能会被截断;使用 usize 可以避免这种情况。

地址到指针转换

从整数转换为原始指针将整数解释为内存地址,并产生引用该内存的指针。

Warning

这与仍在开发中的 Rust 内存模型交互。 从此转换获得的指针即使在位级等于有效指针也可能受到额外限制。 如果不遵循别名规则,解引用此类指针可能是未定义行为

健全地址算术的简单示例:

#![allow(unused)]
fn main() {
let mut values: [i32; 2] = [1, 2];
let p1: *mut i32 = values.as_mut_ptr();
let first_address = p1 as usize;
let second_address = first_address + 4; // 4 == size_of::<i32>()
let p2 = second_address as *mut i32;
unsafe {
    *p2 += 1;
}
assert_eq!(values[1], 3);
}

指针到指针转换

*const T / *mut T 可以转换为 *const U / *mut U,具有以下行为:

  • 如果 TU 都是有大小的,则指针不变返回。

    Example

    #![allow(unused)]
    fn main() {
    let x: i32 = 42;
    let p1: *const i32 = &x;
    let p2: *const u8 = p1 as *const u8;
    // The pointer address remains the same.
    assert_eq!(p1 as usize, p2 as usize);
    }
  • 如果 T 是未大小的,U 是有大小的,则转换丢弃完成宽指针 T 的所有元数据,并产生由未大小指针的数据部分组成的窄指针 U

    Example

    #![allow(unused)]
    fn main() {
    let slice: &[i32] = &[1, 2, 3];
    let ptr: *const [i32] = slice as *const [i32];
    // Cast from wide pointer (*const [i32]) to thin pointer (*const i32)
    // discarding the length metadata.
    let data_ptr: *const i32 = ptr as *const i32;
    assert_eq!(unsafe { *data_ptr }, 1);
    }
  • 如果 TU 都是未大小的,则指针也不变返回。特别是,元数据被精确保留。只有当元数据根据以下规则兼容时,才能执行转换:
  • TU 是具有切片元数据的未大小类型时,它们始终兼容。切片的元数据是元素数量,因此将 *[u16] -> *[u8] 转换是合法的,但会导致字节数减半。

    Example

    #![allow(unused)]
    fn main() {
    let slice: &[u16] = &[1, 2, 3];
    let ptr: *const [u16] = slice as *const [u16];
    let byte_ptr: *const [u8] = ptr as *const [u8];
    assert_eq!(byte_ptr.len(), 3);
    }
  • TU 是具有 trait 对象元数据的未大小类型时,元数据仅在以下所有条件成立时才兼容:
    1. 主要 trait 必须相同。

      Example

      #![allow(unused)]
      fn main() {
      trait Foo {}
      trait Bar {}
      impl Foo for i32 {}
      impl Bar for i32 {}
      
      let x: i32 = 42;
      let ptr_foo: *const dyn Foo = &x as *const dyn Foo;
      // You can't cast to a different principal trait.
      let ptr_bar: *const dyn Bar = ptr_foo as *const dyn Bar; // ERROR
      }
    2. 可以移除自动 trait。

      Example

      #![allow(unused)]
      fn main() {
      trait Foo {}
      struct S;
      impl Foo for S {}
      unsafe impl Send for S {}
      
      let s = S;
      let ptr_send: *const (dyn Foo + Send) = &s;
      // Removing an auto trait.
      let ptr_no_send: *const dyn Foo = ptr_send as *const dyn Foo;
      }
    3. 只有当自动 trait 是主要 trait 的超级 trait 时,才能添加自动 trait。

      Example

      #![allow(unused)]
      fn main() {
      trait Foo: Send {}
      struct S;
      impl Foo for S {}
      unsafe impl Send for S {}
      
      let s = S;
      let ptr_no_send: *const dyn Foo = &s;
      // Adding an auto trait.
      let ptr_send: *const (dyn Foo + Send) = ptr_no_send as *const (dyn Foo + Send);
      }
      #![allow(unused)]
      fn main() {
      trait Foo {}
      struct S;
      impl Foo for S {}
      unsafe impl Send for S {}
      
      let s = S;
      let ptr_no_send: *const dyn Foo = &s;
      // Same as above, except trait Foo does not have Send as a super trait.
      let ptr_send: *const (dyn Foo + Send) = ptr_no_send as *const (dyn Foo + Send); // ERROR
      }
    4. 尾部生命周期只能缩短。

      Example

      #![allow(unused)]
      fn main() {
      trait Foo {}
      
      fn shorten_lifetime<'long: 'short, 'short>(
          ptr: *const (dyn Foo + 'long),
      ) -> *const (dyn Foo + 'short) {
          // Shortening the lifetime is allowed.
          ptr as *const (dyn Foo + 'short)
      }
      }
      #![allow(unused)]
      fn main() {
      trait Foo {}
      
      fn lengthen_lifetime<'long: 'short, 'short>(
          ptr: *const (dyn Foo + 'short),
      ) -> *const (dyn Foo + 'long) {
          // It is not allowed to cast to a longer lifetime.
          ptr as *const (dyn Foo + 'long) // ERROR
      }
      }
    5. 泛型(包括生命周期)和关联类型必须完全匹配。

      Example

      #![allow(unused)]
      fn main() {
      trait Generic<T> {}
      impl Generic<i32> for () {}
      impl Generic<u32> for () {}
      
      let x = ();
      let ptr_i32: *const dyn Generic<i32> = &x;
      // You can't cast to a different generic parameter.
      let ptr_u32: *const dyn Generic<u32> = ptr_i32 as *const dyn Generic<u32>; // ERROR
      }
      #![allow(unused)]
      fn main() {
      trait HasType {
          type Output;
      }
      
      trait Generic<'x, T> {}
      
      fn cast_via_associated<'a, 'b, A, B>(
          ptr: *const dyn Generic<'a, A::Output>,
      ) -> *const dyn Generic<'b, B::Output>
      where
          'a: 'b,
          'b: 'a,
          A: HasType,
          B: HasType<Output = A::Output>, // Forces equality
      {
          ptr as *const dyn Generic<'b, B::Output>
      }
      }
  • TU 是最后一个字段未大小的结构体或元组类型时,它具有与其最后一个字段相同的元数据和兼容性规则。

    Example

    #![allow(unused)]
    fn main() {
    struct Wrapper(u32, [u8]);
    
    let slice: &[u8] = &[1, 2, 3];
    let ptr: *const [u8] = slice;
    
    // The metadata (length 3) is preserved when casting to a struct
    // where the last field is the unsized type `[u8]`.
    let wrapper_ptr: *const Wrapper = ptr as *const Wrapper;
    
    // And preserved when casting back.
    let ptr_back: *const [u8] = wrapper_ptr as *const [u8];
    assert_eq!(ptr_back.len(), 3);
    }

赋值表达式

Syntax
AssignmentExpressionExpression = Expression

赋值表达式将值移入指定位置。

赋值表达式由可变赋值表达式赋值操作数)后跟等号(=)和值表达式赋值值操作数)组成。

在其最基本的形式中,赋值表达式是位置表达式,我们首先讨论这种情况。

下面讨论更一般的解构赋值情况,但这种情况总是分解为对位置表达式的顺序赋值,可以认为是更基本的情况。

基本赋值

求值赋值表达式从求值其操作数开始。首先求值赋值值操作数,然后求值赋值表达式。

对于解构赋值,赋值表达式的子表达式从左到右求值。

Note

这与其他表达式不同,因为右操作数在左操作数之前求值。

然后它具有首先丢弃赋值位置的值的效果,除非该位置是未初始化的局部变量或局部变量的未初始化字段。

接下来,它要么复制或移动赋值值到赋值位置。

赋值表达式始终产生单元值

示例:

#![allow(unused)]
fn main() {
let mut x = 0;
let y = 0;
x = y;
}

解构赋值

解构赋值是变量声明的解构模式匹配的对应物,允许对复杂值(如元组或结构体)进行赋值。例如,我们可以交换两个可变变量:

#![allow(unused)]
fn main() {
let (mut a, mut b) = (0, 1);
// Swap `a` and `b` using destructuring assignment.
(b, a) = (a, b);
}

与使用 let 的解构声明相比,由于语法歧义,模式不能出现在赋值的左侧。相反,一组对应于模式的表达式被指定为赋值表达式,并允许在赋值的左侧。然后赋值表达式被脱糖为模式匹配,然后是顺序赋值。

脱糖的模式必须是不可反驳的:特别是,这意味着只有长度在编译时已知的切片模式和简单切片 [..] 才允许用于解构赋值。

脱糖方法很简单,最好通过示例说明。

#![allow(unused)]
fn main() {
struct Struct { x: u32, y: u32 }
let (mut a, mut b) = (0, 0);
(a, b) = (3, 4);

[a, b] = [3, 4];

Struct { x: a, y: b } = Struct { x: 3, y: 4};

// desugars to:

{
    let (_a, _b) = (3, 4);
    a = _a;
    b = _b;
}

{
    let [_a, _b] = [3, 4];
    a = _a;
    b = _b;
}

{
    let Struct { x: _a, y: _b } = Struct { x: 3, y: 4};
    a = _a;
    b = _b;
}
}

标识符不被禁止在单个赋值表达式中多次使用。

下划线表达式和空范围表达式可用于忽略某些值,而不绑定它们。

请注意,默认绑定模式不适用于脱糖表达式。

Note

脱糖限制了解构赋值的赋值值操作数(RHS)的临时作用域

在基本赋值中,临时值在封闭临时作用域的末尾被丢弃。下面是语句。因此,允许赋值和使用。

#![allow(unused)]
fn main() {
fn temp() {}
fn f<T>(x: T) -> T { x }
let x;
(x = f(&temp()), x); // OK
}

相反,在解构赋值中,临时值在脱糖的 let 语句末尾被丢弃。由于这发生在我们尝试对 x 赋值之前,下面的代码失败。

#![allow(unused)]
fn main() {
fn temp() {}
fn f<T>(x: T) -> T { x }
let x;
[x] = [f(&temp())]; // ERROR
}

这脱糖为:

#![allow(unused)]
fn main() {
fn temp() {}
fn f<T>(x: T) -> T { x }
let x;
{
    let [_x] = [f(&temp())];
    //                     ^
    //      The temporary is dropped here.
    x = _x; // ERROR
}
}

Note

由于脱糖,解构赋值的赋值值操作数(RHS)是新引入的块中的扩展表达式

下面,因为临时作用域被扩展到此引入块的末尾,所以允许赋值。

#![allow(unused)]
fn main() {
fn temp() {}
let x;
[x] = [&temp()]; // OK
}

这脱糖为:

#![allow(unused)]
fn main() {
fn temp() {}
let x;
{ let [_x] = [&temp()]; x = _x; } // OK
}

然而,如果我们尝试使用 x,即使在同一语句中,我们也会收到错误,因为临时值在此引入块的末尾被丢弃。

#![allow(unused)]
fn main() {
fn temp() {}
let x;
([x] = [&temp()], x); // ERROR
}

这脱糖为:

#![allow(unused)]
fn main() {
fn temp() {}
let x;
(
    {
        let [_x] = [&temp()];
        x = _x;
    }, // <-- The temporary is dropped here.
    x, // ERROR
);
}

复合赋值表达式

Syntax
CompoundAssignmentExpression
      Expression += Expression
    | Expression -= Expression
    | Expression *= Expression
    | Expression /= Expression
    | Expression %= Expression
    | Expression &= Expression
    | Expression |= Expression
    | Expression ^= Expression
    | Expression <<= Expression
    | Expression >>= Expression

复合赋值表达式将算术和逻辑二元运算符与赋值表达式结合。

例如:

#![allow(unused)]
fn main() {
let mut x = 5;
x += 1;
assert!(x == 6);
}

复合赋值的语法是可变位置表达式赋值操作数),然后是其中一个运算符后跟 = 作为单个词法单元(无空白),然后是值表达式修改操作数)。

与其他位置操作数不同,赋值位置操作数必须是位置表达式。

尝试使用值表达式是编译器错误,而不是将其提升为临时值。

复合赋值表达式的求值取决于操作数的类型。

如果两个操作数的类型在单态化之前已知为原始类型,则首先求值右侧,然后求值左侧,并通过将运算符应用于两侧的值来修改左侧求值给出的位置。

use core::{num::Wrapping, ops::AddAssign};

trait Equate {}
impl<T> Equate for (T, T) {}

fn f1(x: (u8,)) {
    let mut order = vec![];
    // The RHS is evaluated first as both operands are of primitive
    // type.
    { order.push(2); x }.0 += { order.push(1); x }.0;
    assert!(order.is_sorted());
}

fn f2(x: (Wrapping<u8>,)) {
    let mut order = vec![];
    // The LHS is evaluated first as `Wrapping<_>` is not a primitive
    // type.
    { order.push(1); x }.0 += { order.push(2); (0u8,) }.0;
    assert!(order.is_sorted());
}

fn f3<T: AddAssign<u8> + Copy>(x: (T,)) where (T, u8): Equate {
    let mut order = vec![];
    // The LHS is evaluated first as one of the operands is a generic
    // parameter, even though that generic parameter can be unified
    // with a primitive type due to the where clause bound.
    { order.push(1); x }.0 += { order.push(2); (0u8,) }.0;
    assert!(order.is_sorted());
}

fn main() {
    f1((0u8,));
    f2((Wrapping(0u8),));
    // We supply a primitive type as the generic argument, but this
    // does not affect the evaluation order in `f3` when
    // monomorphized.
    f3::<u8>((0u8,));
}

Note

这是不寻常的。其他地方从左到右求值是规范。

有关更多示例,请参阅求值顺序测试

否则,此表达式是使用运算符对应 trait(参见 expr.arith-logic.behavior)并以其方法作为接收者调用左侧,右侧作为下一个参数的语法糖。

例如,以下两个语句是等效的:

#![allow(unused)]
fn main() {
use std::ops::AddAssign;
fn f<T: AddAssign + Copy>(mut x: T, y: T) {
    x += y; // Statement 1.
    x.add_assign(y); // Statement 2.
}
}

Note

令人惊讶的是,将其进一步脱糖为完全限定的方法调用并不等效,因为当通过 autoref 获取对第一个操作数的可变引用时,借用检查器有特殊行为。

#![allow(unused)]
fn main() {
use std::ops::AddAssign;
fn f<T: AddAssign + Copy>(mut x: T) {
    // Here we used `x` as both the LHS and the RHS. Because the
    // mutable borrow of the LHS needed to call the trait method
    // is taken implicitly by autoref, this is OK.
    x += x; //~ OK
    x.add_assign(x); //~ OK
}
}
#![allow(unused)]
fn main() {
use std::ops::AddAssign;
fn f<T: AddAssign + Copy>(mut x: T) {
    // We can't desugar the above to the below, as once we take the
    // mutable borrow of `x` to pass the first argument, we can't
    // pass `x` by value in the second argument because the mutable
    // reference is still live.
    <T as AddAssign>::add_assign(&mut x, x);
    //~^ ERROR cannot use `x` because it was mutably borrowed
}
}
#![allow(unused)]
fn main() {
use std::ops::AddAssign;
fn f<T: AddAssign + Copy>(mut x: T) {
    // As above.
    (&mut x).add_assign(x);
    //~^ ERROR cannot use `x` because it was mutably borrowed
}
}

与普通赋值表达式一样,复合赋值表达式始终产生单元值

Warning

避免编写依赖于复合赋值中操作数求值顺序的代码,因为它可能不寻常且令人惊讶。


  1. 仅当 m₁mutm₂const 时。允许将 mut 引用/指针转换为 const 指针。 ↩2

  2. 只有不捕获(闭包)任何局部变量的闭包才能转换为函数指针。

分组表达式

Syntax
GroupedExpression( Expression )

括号表达式包装单个表达式,求值为该表达式。括号表达式的语法是 (,然后是表达式(称为封闭操作数),然后是 )

括号表达式求值为封闭操作数的值。

如果封闭操作数是位置表达式,则括号表达式是位置表达式;如果封闭操作数是值表达式,则是值表达式。

括号可用于显式修改表达式内子表达式的优先级顺序。

括号表达式的示例:

#![allow(unused)]
fn main() {
let x: i32 = 2 + 3 * 4; // not parenthesized
let y: i32 = (2 + 3) * 4; // parenthesized
assert_eq!(x, 14);
assert_eq!(y, 20);
}

需要使用括号的示例是当调用作为结构体成员的函数指针时:

#![allow(unused)]
fn main() {
struct A {
   f: fn() -> &'static str
}
impl A {
   fn f(&self) -> &'static str {
       "The method f"
   }
}
let a = A{f: || "The field f"};

assert_eq!( a.f (), "The method f");
assert_eq!((a.f)(), "The field f");
}

数组和数组索引表达式

数组表达式

Syntax
ArrayExpression[ ArrayElements? ]

ArrayElements
      Expression ( , Expression )* ,?
    | Expression ; Expression

数组表达式构造数组。数组表达式有两种形式。

第一种形式列出数组中的每个值。

此形式的语法是用方括号括起来的、逗号分隔的统一类型表达式列表。

这产生一个包含按编写顺序排列的每个值的数组。

第二种形式的语法是用方括号括起来的两个表达式,用分号(;)分隔。

; 之前的表达式称为重复操作数

; 之后的表达式称为长度操作数

长度操作数必须是推断 const 或类型为 usize常量表达式(例如字面量常量项)。

#![allow(unused)]
fn main() {
const C: usize = 1;
let _: [u8; C] = [0; 1]; // Literal.
let _: [u8; C] = [0; C]; // Constant item.
let _: [u8; C] = [0; _]; // Inferred const.
let _: [u8; C] = [0; (((_)))]; // Inferred const.
}

Note

在数组表达式中,推断 const 被解析为表达式,但在语义上被视为单独的const 泛型参数

此形式的数组表达式创建一个数组,其长度为长度操作数的值,每个元素是重复操作数的副本。也就是说,[a; b] 创建一个包含 ba 值副本的数组。

如果长度操作数的值大于 1,则要求重复操作数具有实现 Copy 的类型、是const 块表达式或指向常量项的路径

当重复操作数是 const 块或指向常量项的路径时,它按长度操作数指定的次数求值。

如果该值为 0,则根本不求值 const 块或常量项。

对于既不是 const 块也不是指向常量项路径的表达式,它只求值一次,然后将结果复制长度操作数值的次数。

#![allow(unused)]
fn main() {
[1, 2, 3, 4];
["a", "b", "c", "d"];
[0; 128];              // array with 128 zeros
[0u8, 0u8, 0u8, 0u8,];
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]; // 2D array
const EMPTY: Vec<i32> = Vec::new();
[EMPTY; 2];
}

数组和切片索引表达式

Syntax
IndexExpressionExpression [ Expression ]

数组切片类型的值可以通过在它们后面写一个方括号括起来的类型为 usize 的表达式(索引)来索引。当数组可变时,可以对结果内存位置进行赋值。

对于其他类型,索引表达式 a[b] 等同于 *std::ops::Index::index(&a, b),或在可变位置表达式上下文中等同于 *std::ops::IndexMut::index_mut(&mut a, b),但当索引表达式经历临时生命周期扩展时,被索引的表达式 a临时作用域也会被扩展。与方法一样,Rust 也会在 a 上重复插入解引用操作以查找实现。

#![allow(unused)]
fn main() {
// The temporary holding the result of `vec![()]` is extended to
// live to the end of the block, so `x` may be used in subsequent
// statements.
let x = &vec![()][0];
x;
}
#![allow(unused)]
fn main() {
// The temporary holding the result of `vec![()]` is dropped at the
// end of the statement, so it's an error to use `y` after.
let y = &*std::ops::Index::index(&vec![()], 0); // ERROR
y;
}

数组和切片的索引从零开始。

数组访问是常量表达式,因此可以在编译时使用常量索引值检查边界。否则将在运行时执行检查,如果失败将使线程进入恐慌状态

#![allow(unused)]
fn main() {
// lint is deny by default.
#![warn(unconditional_panic)]

([1, 2, 3, 4])[2];        // Evaluates to 3

let b = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
b[1][2];                  // multidimensional array indexing

let x = (["a", "b"])[10]; // warning: index out of bounds

let n = 10;
let y = (["a", "b"])[n];  // panics

let arr = ["a", "b"];
arr[10];                  // warning: index out of bounds
}

数组索引表达式可以通过实现 IndexIndexMut trait 为数组和切片以外的类型实现。

元组和元组索引表达式

元组表达式

Syntax
TupleExpression( TupleElements? )

TupleElements → ( Expression , )+ Expression?

元组表达式构造元组值

元组表达式的语法是用括号括起来的、逗号分隔的表达式列表,称为元组初始化操作数

一元元组表达式需要在元组初始化操作数后加逗号,以与括号表达式消除歧义。

元组表达式是值表达式,求值为新构造的元组类型值。

元组初始化操作数的数量是构造的元组的元数。

没有任何元组初始化操作数的元组表达式产生单元元组。

对于其他元组表达式,第一个编写的元组初始化操作数初始化字段 0,后续操作数初始化下一个最高字段。例如,在元组表达式 ('a', 'b', 'c') 中,'a' 初始化字段 0 的值,'b' 字段 1'c' 字段 2

元组表达式及其类型的示例:

表达式类型
()()(单元)
(0.0, 4.5)(f64, f64)
("x".to_string(), )(String, )
("a", 4usize, true)(&'static str, usize, bool)

元组索引表达式

Syntax
TupleIndexingExpressionExpression . TUPLE_INDEX

元组索引表达式访问元组元组结构体的字段。

元组索引表达式的语法是一个表达式(称为元组操作数),然后是 .,最后是元组索引。

元组索引的语法是没有前导零、下划线或后缀的十进制字面量。例如 02 是有效的元组索引,但 010_0i32 不是。

元组操作数的类型必须是元组类型元组结构体

元组索引必须是元组操作数类型字段的名称。

元组索引表达式的求值除了求值其元组操作数外没有副作用。作为位置表达式,它求值为与元组索引同名的元组操作数字段的位置。

元组索引表达式的示例:

#![allow(unused)]
fn main() {
// Indexing a tuple
let pair = ("a string", 2);
assert_eq!(pair.1, 2);

// Indexing a tuple struct
struct Point(f32, f32);
let point = Point(1.0, 0.0);
assert_eq!(point.0, 1.0);
assert_eq!(point.1, 0.0);
}

Note

与字段访问表达式不同,元组索引表达式可以是调用表达式的函数操作数,因为它不能与方法调用混淆,因为方法名称不能是数字。

Note

虽然数组和切片也有元素,但必须使用数组或切片索引表达式切片模式来访问它们的元素。

结构体表达式

Syntax
StructExpression
    PathInExpression { ( StructExprFields | StructBase )? }

StructExprFields
    StructExprField ( , StructExprField )* ( , StructBase | ,? )

StructExprField
    OuterAttribute*
    (
        IDENTIFIER
      | ( IDENTIFIER | TUPLE_INDEX ) : Expression
    )

StructBase.. Expression

结构体表达式创建结构体、枚举或联合体值。它由指向结构体枚举变体联合体项的路径后跟该项的字段值组成。

以下是结构体表达式的示例:

#![allow(unused)]
fn main() {
struct Point { x: f64, y: f64 }
struct NothingInMe { }
mod game { pub struct User<'a> { pub name: &'a str, pub age: u32, pub score: usize } }
enum Enum { Variant {} }
Point {x: 10.0, y: 20.0};
NothingInMe {};
let u = game::User {name: "Joe", age: 35, score: 100_000};
Enum::Variant {};
}

Note

元组结构体和元组枚举变体通常使用引用值命名空间中构造函数调用表达式实例化。这与使用花括号引用类型命名空间中构造函数的结构体表达式不同。

#![allow(unused)]
fn main() {
struct Position(i32, i32, i32);
Position(0, 0, 0);  // Typical way of creating a tuple struct.
let c = Position;  // `c` is a function that takes 3 arguments.
let pos = c(8, 6, 7);  // Creates a `Position` value.

enum Version { Triple(i32, i32, i32) };
Version::Triple(0, 0, 0);
let f = Version::Triple;
let ver = f(8, 6, 7);
}

调用路径的最后一段不能引用类型别名:

#![allow(unused)]
fn main() {
trait Tr { type T; }
impl<T> Tr for T { type T = T; }

struct Tuple();
enum Enum { Tuple() }

// <Unit as Tr>::T(); // causes an error -- `::T` is a type, not a value
<Enum as Tr>::T::Tuple(); // OK
}

单元结构体和单元枚举变体通常使用引用值命名空间中常量路径表达式实例化。

#![allow(unused)]
fn main() {
struct Gamma;
// Gamma unit value, referring to the const in the value namespace.
let a = Gamma;
// Exact same value as `a`, but constructed using a struct expression
// referring to the type namespace.
let b = Gamma {};

enum ColorSpace { Oklch }
let c = ColorSpace::Oklch;
let d = ColorSpace::Oklch {};
}

字段结构体表达式

用花括号括起字段的结构体表达式允许您按任何顺序为每个单独的字段指定值。字段名用冒号与其值分隔。

联合体类型的值只能使用此语法创建,并且必须指定恰好一个字段。

函数式更新语法

构造结构体类型值的结构体表达式可以用语法 .. 后跟表达式来终止,以表示函数式更新。

.. 后面的表达式(基)必须与正在形成的新结构体类型具有相同的结构体类型。

整个表达式对指定的字段使用给定的值,并从基表达式移动或复制剩余的字段。

与所有结构体表达式一样,结构体的所有字段必须是可见的,即使是未显式命名的字段。

#![allow(unused)]
fn main() {
struct Point3d { x: i32, y: i32, z: i32 }
let mut base = Point3d {x: 1, y: 2, z: 3};
let y_ref = &mut base.y;
Point3d {y: 0, z: 10, .. base}; // OK, only base.x is accessed
drop(y_ref);
}

结构体表达式不能直接在循环if 表达式的头部,或在 if letmatch 表达式的审查者中使用。但是,如果它们在另一个表达式内部,例如在括号内,则可以在这些情况下使用结构体表达式。

字段名可以是十进制整数值,以指定构造元组结构体的索引。这可以与基结构体一起使用,以填充未指定的剩余索引:

#![allow(unused)]
fn main() {
struct Color(u8, u8, u8);
let c1 = Color(0, 0, 0);  // Typical way of creating a tuple struct.
let c2 = Color{0: 255, 1: 127, 2: 0};  // Specifying fields by index.
let c3 = Color{1: 0, ..c2};  // Fill out all other fields using a base struct.
}

结构体字段初始化简写

当使用命名(但不是编号)字段初始化数据结构(结构体、枚举、联合体)时,允许编写 fieldname 作为 fieldname: fieldname 的简写。这允许使用更紧凑的语法,减少重复。例如:

#![allow(unused)]
fn main() {
struct Point3d { x: i32, y: i32, z: i32 }
let x = 0;
let y_value = 0;
let z = 0;
Point3d { x: x, y: y_value, z: z };
Point3d { x, y: y_value, z };
}

调用表达式

Syntax
CallExpressionExpression ( CallParams? )

CallParamsExpression ( , Expression )* ,?

调用表达式调用函数。调用表达式的语法是一个表达式(称为函数操作数),后跟一个括号括起来的逗号分隔的表达式列表(称为参数操作数)。

如果函数最终返回,则表达式完成。

对于非函数类型,表达式 f(...) 根据函数操作数使用以下 trait 之一的方法:

如果需要,将进行自动借用。函数操作数也将根据需要自动解引用

调用表达式的一些示例:

#![allow(unused)]
fn main() {
fn add(x: i32, y: i32) -> i32 { 0 }
let three: i32 = add(1i32, 2i32);
let name: &'static str = (|| "Rust")();
}

消除函数调用歧义

所有函数调用都是更显式的完全限定语法的语法糖。

根据调用在作用域内项方面的歧义性,函数调用可能需要完全限定。

Note

过去,术语“无歧义函数调用语法“、“通用函数调用语法“或“UFCS“已在文档、issue、RFC 和其他社区文章中使用。然而,这些术语缺乏描述力,并可能混淆当前问题。我们在这里提到它们是为了可搜索性。

经常出现几种情况,导致方法或关联函数调用的接收者或引用者存在歧义。这些情况可能包括:

  • 多个作用域内的 trait 为相同类型定义了同名方法
  • 不希望自动 deref;例如,区分智能指针本身的方法和指针的引用者
  • 不带参数的方法,如 default(),以及返回类型属性的方法,如 size_of()

为了解决歧义,程序员可以使用更具体的路径、类型或 trait 来引用他们想要的方法或函数。

例如,

trait Pretty {
    fn print(&self);
}

trait Ugly {
    fn print(&self);
}

struct Foo;
impl Pretty for Foo {
    fn print(&self) {}
}

struct Bar;
impl Pretty for Bar {
    fn print(&self) {}
}
impl Ugly for Bar {
    fn print(&self) {}
}

fn main() {
    let f = Foo;
    let b = Bar;

    // we can do this because we only have one item called `print` for `Foo`s
    f.print();
    // more explicit, and, in the case of `Foo`, not necessary
    Foo::print(&f);
    // if you're not into the whole brevity thing
    <Foo as Pretty>::print(&f);

    // b.print(); // Error: multiple 'print' found
    // Bar::print(&b); // Still an error: multiple `print` found

    // necessary because of in-scope items defining `print`
    <Bar as Pretty>::print(&b);
}

有关更多详细信息和动机,请参阅 RFC 132

方法调用表达式

Syntax
MethodCallExpressionExpression . PathExprSegment ( CallParams? )

_方法调用_由一个表达式(接收者)后跟单个点、表达式路径段和括号表达式列表组成。

方法调用解析为特定 trait 上的关联方法,如果左侧的确切 self 类型已知,则静态分派到方法,或者如果左侧表达式是间接的 trait 对象,则动态分派。

#![allow(unused)]
fn main() {
let pi: Result<f32, _> = "3.14".parse();
let log_pi = pi.unwrap_or(1.0).log(2.72);
assert!(1.14 < log_pi && log_pi < 1.15)
}

当查找方法调用时,接收者可能会被自动解引用或借用以调用方法。这需要比其他函数更复杂的查找过程,因为可能有许多可能的方法要调用。使用以下过程:

第一步是构建候选接收者类型列表。通过重复解引用接收者表达式的类型来获取这些类型,将遇到的每种类型添加到列表中,然后最后尝试数组未大小强制转换,如果成功则添加结果类型。

然后,对于每个候选 T,在 T 之后立即将 &T&mut T 添加到列表中。

例如,如果接收者类型为 Box<[i32;2]>,则候选类型将是 Box<[i32;2]>&Box<[i32;2]>&mut Box<[i32;2]>[i32; 2](通过解引用)、&[i32; 2]&mut [i32; 2][i32](通过未大小强制转换)、&[i32],最后是 &mut [i32]

然后,对于每种候选类型 T,在以下位置搜索具有该类型接收者的可见方法:

  1. T 的固有方法(直接在 T 上实现的方法)。
  2. T 实现的任何可见 trait 提供的方法。如果 T 是类型参数,则首先查找 T 上 trait 约束提供的方法。然后查找所有剩余的作用域内方法。

Note

按顺序对每种类型进行查找,这偶尔会导致令人惊讶的结果。下面的代码将打印 “In trait impl!”,因为首先查找 &self 方法,在找到结构体的 &mut self 方法之前找到了 trait 方法。

struct Foo {}

trait Bar {
  fn bar(&self);
}

impl Foo {
  fn bar(&mut self) {
    println!("In struct impl!")
  }
}

impl Bar for Foo {
  fn bar(&self) {
    println!("In trait impl!")
  }
}

fn main() {
  let mut f = Foo{};
  f.bar();
}

如果这导致多个可能的候选,则是错误,接收者必须被转换为适当的接收者类型以进行方法调用。

此过程不考虑接收者的可变性或生命周期,也不考虑方法是否是 unsafe。一旦方法被查找,如果由于这些原因中的一个(或多个)而无法调用,结果是编译器错误。

如果达到有多个可能方法的步骤,例如泛型方法或 trait 被认为是相同的,则是编译器错误。这些情况需要消除歧义函数调用语法来调用方法和函数。

2021 Edition differences

在 2021 版本之前,在查找可见方法期间,如果候选接收者类型是数组类型,则忽略标准库 IntoIterator trait 提供的方法。

为此目的使用的版本由表示方法名称的词法单元确定。

此特殊情况将来可能会被移除。

Warning

对于 trait 对象,如果有与 trait 方法同名的固有方法,在方法调用表达式中尝试调用该方法时将产生编译器错误。相反,您可以使用消除歧义函数调用语法调用该方法,在这种情况下它调用 trait 方法,而不是固有方法。无法调用固有方法。只要不要在 trait 对象上定义与 trait 方法同名的固有方法,您就会没事。

字段访问表达式

Syntax
FieldExpressionExpression . IDENTIFIER

字段表达式是求值为结构体联合体字段位置的位置表达式

当操作数是可变的时,字段表达式也是可变的。

字段表达式的语法是一个表达式(称为容器操作数),然后是 .,最后是标识符

字段表达式后面不能跟括号括起来的逗号分隔的表达式列表,因为那会被解析为方法调用表达式。也就是说,它们不能是调用表达式的函数操作数。

Note

将字段表达式包装在括号表达式中以在调用表达式中使用它。

#![allow(unused)]
fn main() {
struct HoldsCallable<F: Fn()> { callable: F }
let holds_callable = HoldsCallable { callable: || () };

// Invalid: Parsed as calling the method "callable"
// holds_callable.callable();

// Valid
(holds_callable.callable)();
}

示例:

mystruct.myfield;
foo().x;
(Struct {a: 10, b: 20}).a;
(mystruct.function_field)() // Call expression containing a field expression

自动解引用

如果容器操作数的类型根据操作数是否可变实现 DerefDerefMut,则会根据需要自动解引用多次以使字段访问成为可能。此过程也简称为autoderef

借用

结构体或对结构体的引用的字段在借用时被视为单独的实体。如果结构体未实现 Drop 并且存储在局部变量中,这也适用于从其每个字段移出。如果通过 Box 以外的用户定义类型进行自动解引用,则也不适用。

#![allow(unused)]
fn main() {
struct A { f1: String, f2: String, f3: String }
let mut x: A;
x = A {
    f1: "f1".to_string(),
    f2: "f2".to_string(),
    f3: "f3".to_string()
};
let a: &mut String = &mut x.f1; // x.f1 borrowed mutably
let b: &String = &x.f2;         // x.f2 borrowed immutably
let c: &String = &x.f2;         // Can borrow again
let d: String = x.f3;           // Move out of x.f3
}

闭包表达式

Syntax
ClosureExpression
    async?1
    move?
    ( || | | ClosureParameters? | )
    ( Expression | -> TypeNoBounds BlockExpression )

ClosureParametersClosureParam ( , ClosureParam )* ,?

ClosureParamOuterAttribute* PatternNoTopAlt ( : Type )?

闭包表达式,也称为 lambda 表达式或 lambda,定义一个闭包类型并求值为该类型的值。闭包表达式的语法是可选的 async 关键字、可选的 move 关键字,然后是管道符号分隔的(|)逗号分隔的模式列表(称为闭包参数),每个模式后面可以选择性地跟 : 和类型,然后是可选的 -> 和类型(称为返回类型),然后是表达式(称为闭包主体操作数)。

每个模式后面的可选类型是模式的类型标注。

如果有返回类型,闭包主体必须是

闭包表达式表示一个函数,它将参数列表映射到参数后面的表达式。就像 [let 绑定]一样,闭包参数是不可反驳的模式,其类型标注是可选的,如果未给出将从上下文推断。

每个闭包表达式都有唯一的匿名类型。

重要的是,闭包表达式_捕获其环境_,而常规函数定义不这样做。

没有 move 关键字时,闭包表达式推断如何从其环境捕获每个变量,优先通过共享引用捕获,有效地借用闭包主体内提到的所有外部变量。

如果需要,编译器将推断应改为获取可变引用,或者应从环境移动或复制值(取决于其类型)。

可以通过在闭包前加上 move 关键字来强制闭包通过复制或移动值来捕获其环境。这通常用于确保闭包的生命周期是 'static

闭包 trait 实现

闭包类型实现哪些 trait 取决于变量的捕获方式、捕获变量的类型以及 async 的存在。有关闭包如何以及何时实现 FnFnMutFnOnce,请参阅调用 trait 和强制转换章节。如果每个捕获变量的类型也实现了该 trait,则闭包类型实现 SendSync

异步闭包

标记有 async 关键字的闭包表示它们是异步的,类似于异步函数

调用异步闭包不执行任何工作,而是求值为实现 Future 的值,该值对应于闭包主体的计算。

#![allow(unused)]
fn main() {
async fn takes_async_callback(f: impl AsyncFn(u64)) {
    f(0).await;
    f(1).await;
}

async fn example() {
    takes_async_callback(async |i| {
        core::future::ready(i).await;
        println!("done with {i}.");
    }).await;
}
}

2018 Edition differences

异步闭包仅从 Rust 2018 版本开始可用。

示例

在此示例中,我们定义了一个函数 ten_times,它接受一个高阶函数参数,然后我们用闭包表达式作为参数调用它,接着是一个从其环境移动值的闭包表达式。

#![allow(unused)]
fn main() {
fn ten_times<F>(f: F) where F: Fn(i32) {
    for index in 0..10 {
        f(index);
    }
}

ten_times(|j| println!("hello, {}", j));
// With type annotations
ten_times(|j: i32| -> () { println!("hello, {}", j) });

let word = "konnichiwa".to_owned();
ten_times(move |j| println!("{}, {}", word, j));
}

闭包参数上的属性

闭包参数上的属性遵循与常规函数参数相同的规则和限制。


  1. async 限定符在 2015 版本中不允许使用。

循环和其他可中断表达式

Syntax
LoopExpression
    LoopLabel? (
        InfiniteLoopExpression
      | PredicateLoopExpression
      | IteratorLoopExpression
      | LabelBlockExpression
    )

Rust 支持四种循环表达式:

所有四种类型的循环都支持 break 表达式标签

除标记块表达式外,所有类型都支持 continue 表达式

只有 loop 和标记块表达式支持求值为非平凡值

无限循环

Syntax
InfiniteLoopExpressionloop BlockExpression

loop 表达式持续重复执行其主体:loop { println!("I live."); }

没有关联 break 表达式的 loop 表达式是发散的,类型为 !

包含关联 break 表达式loop 表达式可能终止,并且其类型必须与 break 表达式的值兼容。

谓词循环

Syntax
PredicateLoopExpressionwhile Conditions BlockExpression

while 循环表达式允许在一组条件保持为 true 时重复求值块。

条件操作数必须是具有布尔类型Expression 或条件 let 匹配。如果所有条件操作数都求值为 true 并且所有 let 模式都成功匹配其审查者,则执行循环主体块。

循环主体成功执行后,重新求值条件操作数以确定是否应再次执行主体。

如果任何条件操作数求值为 false 或任何 let 模式未匹配其审查者,则不执行主体,执行在 while 表达式之后继续。

while 表达式求值为 ()

示例:

#![allow(unused)]
fn main() {
let mut i = 0;

while i < 10 {
    println!("hello");
    i = i + 1;
}
}

while let 模式

while 条件中的 let 模式允许在模式成功匹配时将新变量绑定到作用域中。以下示例说明了使用 let 模式的绑定:

#![allow(unused)]
fn main() {
let mut x = vec![1, 2, 3];

while let Some(y) = x.pop() {
    println!("y = {}", y);
}

while let _ = 5 {
    println!("Irrefutable patterns are always true");
    break;
}
}

while let 循环等同于包含 match 表达式loop 表达式,如下所示。

'label: while let PATS = EXPR {
    /* loop body */
}

等同于

'label: loop {
    match EXPR {
        PATS => { /* loop body */ },
        _ => break,
    }
}

可以使用 | 运算符指定多个模式。这与 match 表达式中的 | 具有相同的语义:

#![allow(unused)]
fn main() {
let mut vals = vec![2, 3, 1, 2, 2];
while let Some(v @ 1) | Some(v @ 2) = vals.pop() {
    // Prints 2, 2, then 1
    println!("{}", v);
}
}

while 条件链

多个条件操作数可以用 && 分隔。这些与 if 条件链具有相同的语义和限制。

以下是链接多个表达式、混合 let 绑定和布尔表达式、以及表达式能够引用先前表达式的模式绑定的示例:

fn main() {
    let outer_opt = Some(Some(1i32));

    while let Some(inner_opt) = outer_opt
        && let Some(number) = inner_opt
        && number == 1
    {
        println!("Peek a boo");
        break;
    }
}

迭代器循环

Syntax
IteratorLoopExpression
    for Pattern in Expressionexcept StructExpression BlockExpression

for 表达式是一种语法构造,用于循环遍历 std::iter::IntoIterator 实现提供的元素。

如果迭代器产生一个值,则将该值与不可反驳模式匹配,执行循环主体,然后控制返回到 for 循环的头部。如果迭代器为空,则 for 表达式完成。

遍历数组内容的 for 循环示例:

#![allow(unused)]
fn main() {
let v = &["apples", "cake", "coffee"];

for text in v {
    println!("I like {}.", text);
}
}

遍历一系列整数的 for 循环示例:

#![allow(unused)]
fn main() {
let mut sum = 0;
for n in 1..11 {
    sum += n;
}
assert_eq!(sum, 55);
}

for 循环等同于包含 match 表达式loop 表达式,如下所示:

'label: for PATTERN in iter_expr {
    /* loop body */
}

等同于

{
    let result = match IntoIterator::into_iter(iter_expr) {
        mut iter => 'label: loop {
            let mut next;
            match Iterator::next(&mut iter) {
                Option::Some(val) => next = val,
                Option::None => break,
            };
            let PATTERN = next;
            let () = { /* loop body */ };
        },
    };
    result
}

IntoIteratorIteratorOption 始终是此处的标准库项,而不是这些名称在当前作用域中解析的任何内容。

变量名称 nextiterval 仅用于说明,它们实际上没有用户可以输入的名称。

Note

外部 match 用于确保 iter_expr 中的任何临时值在循环完成之前不会被丢弃。next 在赋值之前声明,因为这会导致类型更经常被正确推断。

循环标签

Syntax
LoopLabelLIFETIME_OR_LABEL :

循环表达式可以选择具有_标签_。标签写在循环表达式之前的生命周期,如 'foo: loop { break 'foo; }'bar: while false {}'humbug: for _ in 0..0 {}

如果存在标签,则嵌套在此循环内的标记 breakcontinue 表达式可以退出此循环或将其控制返回到其头部。参见 break 表达式continue 表达式

标签遵循局部变量的卫生和遮蔽规则。例如,此代码将打印 “outer loop”:

#![allow(unused)]
fn main() {
'a: loop {
    'a: loop {
        break 'a;
    }
    print!("outer loop");
    break 'a;
}
}

'_ 不是有效的循环标签。

break 表达式

Syntax
BreakExpressionbreak LIFETIME_OR_LABEL? Expression?

遇到 break 时,关联循环主体的执行立即终止,例如:

#![allow(unused)]
fn main() {
let mut last = 0;
for x in 1..100 {
    if x > 12 {
        break;
    }
    last = x;
}
assert_eq!(last, 12);
}

break 表达式是发散的,类型为 !

break 表达式通常与包含 break 表达式的最内层 loopforwhile 循环关联,但可以使用标签来指定受影响的封闭循环。示例:

#![allow(unused)]
fn main() {
'outer: loop {
    while true {
        break 'outer;
    }
}
}

break 表达式只允许在循环主体中,具有 breakbreak 'label 或(见下文break EXPRbreak 'label EXPR 形式之一。

带 break 表达式的 loop标记块表达式中,没有表达式的 break 等同于 break ()

标记块表达式

Syntax
LabelBlockExpressionBlockExpression

标记块表达式与块表达式完全相同,只是它们允许在块内使用 break 表达式。

与循环不同,标记块表达式内的 break 表达式必须有标签(即标签不是可选的)。

类似地,标记块表达式必须以标签开头。

#![allow(unused)]
fn main() {
fn do_thing() {}
fn condition_not_met() -> bool { true }
fn do_next_thing() {}
fn do_last_thing() {}
let result = 'block: {
    do_thing();
    if condition_not_met() {
        break 'block 1;
    }
    do_next_thing();
    if condition_not_met() {
        break 'block 2;
    }
    do_last_thing();
    3
};
}

标记块表达式的类型是所有 break 操作数和最终操作数的最小上界。如果省略最终操作数,则最终操作数的类型默认为单元类型,除非块发散,在这种情况下它是永不类型

Example

#![allow(unused)]
fn main() {
fn example(condition: bool) {
    let s = String::from("owned");

    let _: &str = 'block: {
        if condition {
            break 'block &s;  // &String coerced to &str via Deref
        }
        break 'block "literal";  // &'static str coerced to &str
    };
}
}

continue 表达式

Syntax
ContinueExpressioncontinue LIFETIME_OR_LABEL?

遇到 continue 时,关联循环主体的当前迭代立即终止,将控制返回到循环头部

continue 表达式是发散的,类型为 !

对于 while 循环,头部是控制循环的条件操作数。

对于 for 循环,头部是控制循环的调用表达式。

break 一样,continue 通常与最内层封闭循环关联,但 continue 'label 可用于指定受影响的循环。

continue 表达式只允许在循环主体中。

break 和循环值

当与 loop 关联时,break 表达式可用于从该循环返回值,形式为 break EXPRbreak 'label EXPR,其中 EXPR 是从 loop 返回的表达式的结果。例如:

#![allow(unused)]
fn main() {
let (mut a, mut b) = (1, 1);
let result = loop {
    if b > 10 {
        break b;
    }
    let c = a + b;
    a = b;
    b = c;
};
// first number in Fibonacci sequence over 10:
assert_eq!(result, 13);
}

带有关联 break 表达式的 loop 的类型是所有 break 操作数的最小上界

Example

#![allow(unused)]
fn main() {
fn example(condition: bool) {
    let s = String::from("owned");

    let _: &str = loop {
        if condition {
            break &s; // &String coerced to &str via Deref
        }
        break "literal"; // &'static str coerced to &str
    };
}
}

如果任何 break 操作数不发散,则带有关联 break 表达式的 loop发散。如果所有 break 操作数都发散,则 loop 表达式也发散。

Example

#![allow(unused)]
fn main() {
fn diverging_loop_with_break(condition: bool) -> ! {
    // This loop is diverging because all `break` operands are diverging.
    loop {
        if condition {
            break loop {};
        } else {
            break panic!();
        }
    }
}
}
#![allow(unused)]
fn main() {
fn loop_with_non_diverging_break(condition: bool) -> ! {
    // The type of this loop is i32 even though one of the breaks is
    // diverging.
    loop {
        if condition {
            break loop {};
        } else {
            break 123i32;
        }
    } // ERROR: expected `!`, found `i32`
}
}

范围表达式

....= 运算符将根据下表构造 std::ops::Range(或 core::ops::Range)变体之一的对象:

产生式语法类型范围
RangeExprstart..endstd::ops::Rangestart ≤ x < end
RangeFromExprstart..std::ops::RangeFromstart ≤ x
RangeToExpr..endstd::ops::RangeTox < end
RangeFullExpr..std::ops::RangeFull-
RangeInclusiveExprstart..=endstd::ops::RangeInclusivestart ≤ x ≤ end
RangeToInclusiveExpr..=endstd::ops::RangeToInclusivex ≤ end

示例:

#![allow(unused)]
fn main() {
1..2;   // std::ops::Range
3..;    // std::ops::RangeFrom
..4;    // std::ops::RangeTo
..;     // std::ops::RangeFull
5..=6;  // std::ops::RangeInclusive
..=7;   // std::ops::RangeToInclusive
}

以下表达式是等效的。

#![allow(unused)]
fn main() {
let x = std::ops::Range {start: 0, end: 10};
let y = 0..10;

assert_eq!(x, y);
}

范围可以在 for 循环中使用:

#![allow(unused)]
fn main() {
for i in 1..11 {
    println!("{}", i);
}
}

if 表达式

if 表达式的语法是一个或多个用 && 分隔的条件操作数序列,后跟结果块、任意数量的 else if 条件和块,以及可选的尾部 else 块。

条件操作数必须是具有布尔类型Expression 或条件 let 匹配。

如果所有条件操作数都求值为 true 并且所有 let 模式都成功匹配其审查者,则执行结果块并跳过任何后续的 else ifelse 块。

如果任何条件操作数求值为 false 或任何 let 模式未匹配其审查者,则跳过结果块并求值任何后续的 else if 条件。

如果所有 ifelse if 条件都求值为 false,则执行任何 else 块。

if 表达式求值为与执行的块相同的值,如果没有块被求值则为 ()

if 表达式在所有情况下必须具有相同的类型。

#![allow(unused)]
fn main() {
let x = 3;
if x == 4 {
    println!("x is four");
} else if x == 3 {
    println!("x is three");
} else {
    println!("x is something else");
}

// `if` can be used as an expression.
let y = if 12 * 15 > 150 {
    "Bigger"
} else {
    "Smaller"
};
assert_eq!(y, "Bigger");
}

如果条件表达式发散或所有分支都发散,则 if 表达式发散

#![allow(unused)]
fn main() {
fn diverging_condition() -> ! {
    // Diverges because the condition expression diverges
    if loop {} {
        ()
    } else {
        ()
    };
    // The semicolon above is important: The type of the `if` expression is
    // `()`, despite being diverging. When the final body expression is
    // elided, the type of the body is inferred to ! because the function body
    // diverges. Without the semicolon, the `if` would be the tail expression
    // with type `()`, which would fail to match the return type `!`.
}

fn diverging_arms() -> ! {
    // Diverges because all arms diverge
    if true {
        loop {}
    } else {
        loop {}
    }
}
}

if let 模式

if 条件中的 let 模式允许在模式成功匹配时将新变量绑定到作用域中。

以下示例说明了使用 let 模式的绑定:

#![allow(unused)]
fn main() {
let dish = ("Ham", "Eggs");

// This body will be skipped because the pattern is refuted.
if let ("Bacon", b) = dish {
    println!("Bacon is served with {}", b);
} else {
    // This block is evaluated instead.
    println!("No bacon will be served");
}

// This body will execute.
if let ("Ham", b) = dish {
    println!("Ham is served with {}", b);
}

if let _ = 5 {
    println!("Irrefutable patterns are always true");
}
}

可以使用 | 运算符指定多个模式。这与 [match 表达式]中的 | 具有相同的语义:

#![allow(unused)]
fn main() {
enum E {
    X(u8),
    Y(u8),
    Z(u8),
}
let v = E::Y(12);
if let E::X(n) | E::Y(n) = v {
    assert_eq!(n, 12);
}
}

条件链

多个条件操作数可以用 && 分隔。

类似于 && 惰性布尔表达式,每个操作数从左到右求值,直到操作数求值为 falselet 匹配失败,在这种情况下不求值后续操作数。

每个模式的绑定被放入作用域,以供下一个条件操作数和结果块使用。

以下是链接多个表达式、混合 let 绑定和布尔表达式、以及表达式能够引用先前表达式的模式绑定的示例:

#![allow(unused)]
fn main() {
fn single() {
    let outer_opt = Some(Some(1i32));

    if let Some(inner_opt) = outer_opt
        && let Some(number) = inner_opt
        && number == 1
    {
        println!("Peek a boo");
    }
}
}

上面的代码等同于以下不使用条件链的代码:

#![allow(unused)]
fn main() {
fn nested() {
    let outer_opt = Some(Some(1i32));

    if let Some(inner_opt) = outer_opt {
        if let Some(number) = inner_opt {
            if number == 1 {
                println!("Peek a boo");
            }
        }
    }
}
}

如果任何条件操作数是 let 模式,则由于 let 审查者的歧义和优先级,任何条件操作数都不能是 || 惰性布尔运算符表达式

Example

如果需要 || 表达式,则可以使用括号。例如:

#![allow(unused)]
fn main() {
let foo = Some(123);
let condition1 = true;
let condition2 = false;
if let Some(x) = foo
    // Parentheses are required here.
    && (condition1 || condition2)
{}
}

2024 Edition differences

在 2024 版本之前,不支持 let 链。也就是说,if 表达式中不允许使用 LetChain 语法。

match 表达式

match 表达式在模式上分支。发生的匹配的确切形式取决于模式

match 表达式有一个*审查者表达式*,即要与模式进行比较的值。

审查者表达式和模式必须具有相同的类型。

match 的行为根据审查者表达式是位置表达式还是值表达式而有所不同。

如果审查者表达式是值表达式,它首先被求值到临时位置,然后将结果值与分支中的模式依次比较,直到找到匹配。选择具有匹配模式的第一个分支作为 match 的分支目标,模式绑定的任何变量被赋值给分支块中的局部变量,控制进入该块。

当审查者表达式是位置表达式时,匹配不会分配临时位置;但是,按值绑定可能会从内存位置复制或移动。如果可能,最好对位置表达式进行匹配,因为这些匹配的生命周期继承位置表达式的生命周期,而不是被限制在 match 内部。

match 表达式的示例:

#![allow(unused)]
fn main() {
let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    _ => println!("something else"),
}
}

模式内绑定的变量在 match 守卫和分支表达式的作用域内。

绑定模式(移动、复制或引用)取决于模式。

多个匹配模式可以用 | 运算符连接。将按从左到右的顺序测试每个模式,直到找到成功的匹配。

#![allow(unused)]
fn main() {
let x = 9;
let message = match x {
    0 | 1  => "not many",
    2 ..= 9 => "a few",
    _      => "lots"
};

assert_eq!(message, "a few");

// Demonstration of pattern match order.
struct S(i32, i32);

match S(1, 2) {
    S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
    _ => panic!(),
}
}

Note

2..=9范围模式,而不是范围表达式。因此,只有范围模式支持的范围类型才能在 match 分支中使用。

每个 | 分隔的模式中的每个绑定必须出现在分支中的所有模式中。

同名的每个绑定必须具有相同的类型和相同的绑定模式。

整个 match 表达式的类型是各个 match 分支的最小上界

如果没有 match 分支,则 match 表达式是发散的,类型为 !

Example

#![allow(unused)]
fn main() {
fn make<T>() -> T { loop {} }
enum Empty {}

fn diverging_match_no_arms() -> ! {
    let e: Empty = make();
    match e {}
}
}

如果审查者表达式或所有 match 分支都发散,则整个 match 表达式也发散。

Match 守卫

Match 分支可以接受 _match 守卫_以进一步细化匹配情况的标准。

模式守卫出现在 if 关键字后面的模式之后,由具有布尔类型Expression 或条件 let 匹配组成。

当模式成功匹配时,执行模式守卫。如果所有守卫条件操作数都求值为 true 并且所有 let 模式都成功匹配其审查者,则 match 分支被成功匹配,执行分支主体。

否则,测试下一个模式,包括同一分支中使用 | 运算符的其他匹配。

#![allow(unused)]
fn main() {
let maybe_digit = Some(0);
fn process_digit(i: i32) { }
fn process_other(i: i32) { }
let message = match maybe_digit {
    Some(x) if x < 10 => process_digit(x),
    Some(x) => process_other(x),
    None => panic!(),
};
}

Note

使用 | 运算符的多个匹配可能导致模式守卫及其副作用多次执行。例如:

#![allow(unused)]
fn main() {
use std::cell::Cell;
let i : Cell<i32> = Cell::new(0);
match 1 {
    1 | _ if { i.set(i.get() + 1); false } => {}
    _ => {}
}
assert_eq!(i.get(), 2);
}

模式守卫可以引用它们跟随的模式内绑定的变量。

在求值守卫之前,获取变量匹配的审查者部分的共享引用。在求值守卫期间,访问变量时使用此共享引用。

只有当守卫成功求值时,才会从审查者移动或复制值到变量中。这允许在守卫内部使用共享借用,而不会在守卫匹配失败时从审查者移出。

此外,通过在求值守卫时持有共享引用,也防止了守卫内部的修改。

守卫可以使用 let 模式来有条件地匹配审查者,并在模式成功匹配时将新变量绑定到作用域中。

Example

在此示例中,求值守卫条件 let Some(first_char) = name.chars().next()。如果 let 模式成功匹配(即字符串至少有一个字符),则执行分支的主体。否则,模式匹配继续到下一个分支。

let 模式创建一个新的绑定(first_char),可以在分支的主体中与原始模式绑定(name)一起使用。

#![allow(unused)]
fn main() {
enum Command {
    Run(String),
    Stop,
}
let cmd = Command::Run("example".to_string());

match cmd {
    Command::Run(name) if let Some(first_char) = name.chars().next() => {
        // Both `name` and `first_char` are available here
        println!("Running: {name} (starts with '{first_char}')");
    }
    Command::Run(name) => {
        println!("{name} is empty");
    }
    _ => {}
}
}

Match 守卫链

多个守卫条件操作数可以用 && 分隔。

Example

#![allow(unused)]
fn main() {
let foo = Some([123]);
let already_checked = false;
match foo {
    Some(xs) if let [single] = xs && !already_checked => { dbg!(single); }
    _ => {}
}
}

类似于 && 惰性布尔表达式,每个操作数从左到右求值,直到操作数求值为 falselet 匹配失败,在这种情况下不求值后续操作数。

每个 let 模式的绑定被放入作用域,以供下一个条件操作数和 match 分支主体使用。

如果任何守卫条件操作数是 let 模式,则由于 let 审查者的歧义和优先级,任何条件操作数都不能是 || 惰性布尔运算符表达式

Example

如果需要 || 表达式,则可以使用括号。例如:

#![allow(unused)]
fn main() {
let foo = Some([123]);
match foo {
    Some(xs) if let [x] = xs
        // Parentheses are required here.
        && (x < -100 || x > 20) => {}
    _ => {}
}
}

Match 分支上的属性

match 分支上允许使用外部属性。在 match 分支上有意义的唯一属性是 cfglint 检查属性

在与块表达式上的属性相同的表达式上下文中,match 表达式的开花括号之后直接允许使用内部属性

return 表达式

Syntax
ReturnExpressionreturn Expression?

Return 表达式用关键字 return 表示。

求值 return 表达式会将其参数移入当前函数调用的指定输出位置,销毁当前函数激活帧,并将控制转移到调用者帧。

return 表达式是发散的,类型为 !

return 表达式的示例:

#![allow(unused)]
fn main() {
fn max(a: i32, b: i32) -> i32 {
    if a > b {
        return a;
    }
    return b;
}
}

Await 表达式

Syntax
AwaitExpressionExpression . await

await 表达式是一种语法构造,用于挂起由 std::future::IntoFuture 实现提供的计算,直到给定的 future 准备好产生值。

await 表达式的语法是一个类型实现 IntoFuture trait 的表达式(称为 future 操作数),然后是词法单元 .,然后是 await 关键字。

Await 表达式仅在异步上下文中合法,如 async fn、[async 闭包]或 [async 块]。

更具体地说,await 表达式具有以下效果。

  1. 通过对 future 操作数调用 IntoFuture::into_future 来创建 future。
  2. 将 future 求值为 future tmp
  3. 使用 Pin::new_unchecked 固定 tmp
  4. 然后通过调用 Future::poll 方法并将当前任务上下文传递给它来轮询此固定的 future;
  5. 如果对 poll 的调用返回 Poll::Pending,则 future 返回 Poll::Pending,挂起其状态,以便当周围的异步上下文被重新轮询时,执行返回到步骤 3;
  6. 否则对 poll 的调用必须已返回 Poll::Ready,在这种情况下,Poll::Ready 变体中包含的值用作 await 表达式本身的结果。

2018 Edition differences

Await 表达式仅从 Rust 2018 版本开始可用。

任务上下文

任务上下文指的是当异步上下文本身被轮询时提供给当前异步上下文Context。因为 await 表达式仅在异步上下文中合法,所以必须有某些任务上下文可用。

近似脱糖

实际上,await 表达式大致等同于以下非规范性脱糖:

match operand.into_future() {
    mut pinned => loop {
        let mut pin = unsafe { Pin::new_unchecked(&mut pinned) };
        match Pin::future::poll(Pin::borrow(&mut pin), &mut current_context) {
            Poll::Ready(r) => break r,
            Poll::Pending => yield Poll::Pending,
        }
    }
}

其中 yield 伪代码返回 Poll::Pending,当重新调用时,从该点恢复执行。变量 current_context 指的是从异步环境获取的上下文。

_ 表达式

Syntax
UnderscoreExpression_

下划线表达式,用符号 _ 表示,用于在解构赋值中表示占位符。

它们只能出现在赋值的左侧。

请注意,这与[通配符模式][wildcard pattern]不同。

_ 表达式的示例:

#![allow(unused)]
fn main() {
let p = (1, 2);
let mut a = 0;
(_, a) = p;

struct Position {
    x: u32,
    y: u32,
}

Position { x: a, y: _ } = Position{ x: 2, y: 3 };

// unused result, assignment to `_` used to declare intent and remove a warning
_ = 2 + 2;
// triggers unused_must_use warning
// 2 + 2;

// equivalent technique using a wildcard pattern in a let-binding
let _ = 2 + 2;
}

模式

Syntax
Pattern|? PatternNoTopAlt ( | PatternNoTopAlt )*

PatternNoTopAlt
      PatternWithoutModernRange
    | ModernRangePattern

PatternWithoutModernRange
      LiteralPattern
    | IdentifierPattern
    | WildcardPattern
    | RestPattern
    | ReferencePattern
    | StructPattern
    | TupleStructPattern
    | TuplePattern
    | GroupedPattern
    | SlicePattern
    | PathPattern
    | MacroInvocation
    | ObsoleteRangePattern1

模式(Pattern)用于将值与结构进行匹配,并可选择将变量绑定到这些结构内的值。它们也用于变量声明以及函数和闭包的参数中。

以下示例中的模式做了四件事:

  • 测试 personcar 字段是否填充了某些内容。
  • 测试人的 age 字段是否在 13 到 19 之间,并将其值绑定到 person_age 变量。
  • 将对 name 字段的引用绑定到变量 person_name
  • 忽略 person 的其余字段。其余字段可以有任何值,不会绑定到任何变量。
#![allow(unused)]
fn main() {
struct Car;
struct Computer;
struct Person {
    name: String,
    car: Option<Car>,
    computer: Option<Computer>,
    age: u8,
}
let person = Person {
    name: String::from("John"),
    car: Some(Car),
    computer: None,
    age: 15,
};
if let
    Person {
        car: Some(_),
        age: person_age @ 13..=19,
        name: ref person_name,
        ..
    } = person
{
    println!("{} has a car and is {} years old.", person_name, person_age);
}
}

模式用于:

  • 函数和[闭包][closure]参数

解构

模式可用于*[解构][destructure]*结构体枚举元组。解构将值分解为其组成部分。使用的语法与创建此类值时几乎相同。

审查者表达式具有 structenumtuple 类型的模式中,[通配符模式][wildcard pattern](_)代表单个数据字段,而[等等][et cetera]或[休息模式][rest pattern](..)代表特定变体的所有剩余字段。

当解构具有命名(但不是编号)字段的数据结构时,允许编写 fieldname 作为 fieldname: fieldname 的简写。

#![allow(unused)]
fn main() {
enum Message {
    Quit,
    WriteString(String),
    Move { x: i32, y: i32 },
    ChangeColor(u8, u8, u8),
}
let message = Message::Quit;
match message {
    Message::Quit => println!("Quit"),
    Message::WriteString(write) => println!("{}", &write),
    Message::Move{ x, y: 0 } => println!("move {} horizontally", x),
    Message::Move{ .. } => println!("other move"),
    Message::ChangeColor { 0: red, 1: green, 2: _ } => {
        println!("color change, red: {}, green: {}", red, green);
    }
};
}

可反驳性

当模式有可能不被它正在匹配的值匹配时,该模式被称为可反驳的。另一方面,不可反驳的模式总是匹配它们正在匹配的值。示例:

#![allow(unused)]
fn main() {
let (x, y) = (1, 2);               // "(x, y)" is an irrefutable pattern

if let (a, 3) = (1, 2) {           // "(a, 3)" is refutable, and will not match
    panic!("Shouldn't reach here");
} else if let (a, 4) = (3, 4) {    // "(a, 4)" is refutable, and will match
    println!("Matched ({}, 4)", a);
}
}

字面量模式

Syntax
LiteralPattern-? LiteralExpression

_字面量模式_精确匹配与字面量创建的值相同的值。由于负数不是字面量,模式中的字面量可以在前面加上可选的减号,其作用类似于取反运算符。

Warning

C 字符串和原始 C 字符串字面量在字面量模式中被接受,但 &CStr 不实现结构相等性(#[derive(Eq, PartialEq)]),因此任何对 &CStrmatch 都将因类型错误而被拒绝。

字面量模式总是可反驳的。

示例:

#![allow(unused)]
fn main() {
for i in -2..5 {
    match i {
        -1 => println!("It's minus one"),
        1 => println!("It's a one"),
        2|4 => println!("It's either a two or a four"),
        _ => println!("Matched none of the arms"),
    }
}
}

标识符模式

Syntax
IdentifierPatternref? mut? IDENTIFIER ( @ PatternNoTopAlt )?

标识符模式将它们匹配的值绑定到值命名空间中的变量。

标识符在模式内必须唯一。

变量将遮蔽作用域中同名的任何变量。新绑定的作用域取决于使用模式的上下文(如 let 绑定或 match 分支)。

仅由标识符组成的模式(可能带有 mut)匹配任何值并将其绑定到该标识符。这是变量声明以及函数和闭包参数中最常用的模式。

#![allow(unused)]
fn main() {
let mut variable = 10;
fn sum(x: i32, y: i32) -> i32 {
   x + y
}
}

要将模式的匹配值绑定到变量,请使用语法 variable @ subpattern。例如,以下代码将值 2 绑定到 e(不是整个范围:这里的范围是范围子模式)。

#![allow(unused)]
fn main() {
let x = 2;

match x {
    e @ 1 ..= 5 => println!("got a range element {}", e),
    _ => println!("anything"),
}
}

默认情况下,标识符模式根据匹配值是否实现 Copy 将变量绑定到匹配值的副本或从匹配值移动。

这可以通过使用 ref 关键字更改为绑定到引用,或使用 ref mut 绑定到可变引用。例如:

#![allow(unused)]
fn main() {
let a = Some(10);
match a {
    None => (),
    Some(value) => (),
}

match a {
    None => (),
    Some(ref value) => (),
}
}

在第一个 match 表达式中,值被复制(或移动)。在第二个 match 中,对同一内存位置的引用被绑定到变量值。需要此语法是因为在解构子模式中,& 运算符不能应用于值的字段。例如,以下代码无效:

#![allow(unused)]
fn main() {
struct Person {
   name: String,
   age: u8,
}
let value = Person { name: String::from("John"), age: 23 };
if let Person { name: &person_name, age: 18..=150 } = value { }
}

要使其有效,请编写以下代码:

#![allow(unused)]
fn main() {
struct Person {
   name: String,
   age: u8,
}
let value = Person { name: String::from("John"), age: 23 };
if let Person { name: ref person_name, age: 18..=150 } = value { }
}

因此,ref 不是正在匹配的内容。其目的完全是为了使匹配的绑定成为引用,而不是潜在地复制或移动被匹配的内容。

[路径模式][Path patterns]优先于标识符模式。

Note

当模式是单段标识符时,语法上存在歧义,不知道它是 IdentifierPattern 还是 PathPattern。此歧义只能在名称解析之后解决。

#![allow(unused)]
fn main() {
const EXPECTED_VALUE: u8 = 42;
//    ^^^^^^^^^^^^^^ That this constant is in scope affects how the
//                   patterns below are treated.

fn check_value(x: u8) -> Result<u8, u8> {
    match x {
        EXPECTED_VALUE => Ok(x),
    //  ^^^^^^^^^^^^^^ Parsed as a `PathPattern` that resolves to
    //                 the constant `42`.
        other_value => Err(x),
    //  ^^^^^^^^^^^ Parsed as an `IdentifierPattern`.
    }
}

// If `EXPECTED_VALUE` were treated as an `IdentifierPattern` above,
// that pattern would always match, making the function always return
// `Ok(_) regardless of the input.
assert_eq!(check_value(42), Ok(42));
assert_eq!(check_value(43), Err(43));
}

如果指定了 refref mut 并且标识符遮蔽了常量,则是错误。

如果 @ 子模式是不可反驳的或未指定子模式,则标识符模式是不可反驳的。

绑定模式

为了更好的人体工程学,模式在不同的绑定模式下操作,以便更容易地将引用绑定到值。当引用值被非引用模式匹配时,它将自动被视为 refref mut 绑定。示例:

#![allow(unused)]
fn main() {
let x: &Option<i32> = &Some(3);
if let Some(y) = x {
    // y was converted to `ref y` and its type is &i32
}
}

非引用模式包括除绑定、[通配符模式][wildcard pattern](_)、引用类型的 [const 模式][const patterns]和[引用模式][reference patterns]之外的所有模式。

如果绑定模式没有显式具有 refref mutmut,则它使用默认绑定模式来确定如何绑定变量。

默认绑定模式从“移动“模式开始,使用移动语义。

匹配模式时,编译器从模式外部开始并向内工作。

每次使用非引用模式匹配引用时,它将自动解引用值并更新默认绑定模式。

引用将默认绑定模式设置为 ref

可变引用将模式设置为 ref mut,除非模式已经是 ref,在这种情况下它保持 ref

如果自动解引用的值仍然是引用,则它被解引用,此过程重复。

只有当默认绑定模式是“移动“时,绑定模式才能显式指定 refref mut 绑定模式,或使用 mut 指定可变性。例如,这些不被接受:

#![allow(unused)]
fn main() {
let [mut x] = &[()]; //~ ERROR
let [ref x] = &[()]; //~ ERROR
let [ref mut x] = &mut [()]; //~ ERROR
}

2024 Edition differences

在 2024 版本之前,即使默认绑定模式不是“移动“,绑定也可以显式指定 refref mut 绑定模式,并且可以使用 mut 在此类绑定上指定可变性。在这些版本中,在绑定上指定 mut 会将绑定模式设置为“移动“,而不管当前的默认绑定模式如何。

类似地,引用模式只能在默认绑定模式是“移动“时出现。例如,这不被接受:

#![allow(unused)]
fn main() {
let [&x] = &[&()]; //~ ERROR
}

2024 Edition differences

在 2024 版本之前,即使默认绑定模式不是“移动“,引用模式也可以出现,并且具有匹配审查者和导致默认绑定模式重置为“移动“的双重效果。

移动绑定和引用绑定可以在同一模式中混合使用。这样做会导致绑定对象的部分移动,并且之后不能使用该对象。这仅适用于类型不能复制的情况。

在下面的示例中,nameperson 移出。尝试将 person 作为整体使用或 person.name 会因部分移动而导致错误。

示例:

#![allow(unused)]
fn main() {
struct Person {
   name: String,
   age: u8,
}
let person = Person{ name: String::from("John"), age: 23 };
// `name` is moved from person and `age` referenced
let Person { name, ref age } = person;
}

通配符模式

Syntax
WildcardPattern_

通配符模式(下划线符号)匹配任何值。当值不重要时,用于忽略值。

在其他模式内部,它匹配单个数据字段(与 .. 相反,后者匹配剩余字段)。

与标识符模式不同,它不复制、移动或借用它匹配的值。

示例:

#![allow(unused)]
fn main() {
let x = 20;
let (a, _) = (10, x);   // the x is always matched by _
assert_eq!(a, 10);

// ignore a function/closure param
let real_part = |a: f64, _: f64| { a };

// ignore a field from a struct
struct RGBA {
   r: f32,
   g: f32,
   b: f32,
   a: f32,
}
let color = RGBA{r: 0.4, g: 0.1, b: 0.9, a: 0.5};
let RGBA{r: red, g: green, b: blue, a: _} = color;
assert_eq!(color.r, red);
assert_eq!(color.g, green);
assert_eq!(color.b, blue);

// accept any Some, with any value
let x = Some(10);
if let Some(_) = x {}
}

通配符模式总是不可反驳的。

休息模式

Syntax
RestPattern..

休息模式.. 词法单元)充当可变长度模式,匹配之前和之后尚未匹配的零个或多个元素。

它只能在元组、[元组结构体][tuple struct]和切片模式中使用,并且只能作为这些模式中的元素之一出现一次。它也仅在[切片模式][slice patterns]的[标识符模式][identifier pattern]中允许。

休息模式总是不可反驳的。

示例:

#![allow(unused)]
fn main() {
let words = vec!["a", "b", "c"];
let slice = &words[..];
match slice {
    [] => println!("slice is empty"),
    [one] => println!("single element {}", one),
    [head, tail @ ..] => println!("head={} tail={:?}", head, tail),
}

match slice {
    // Ignore everything but the last element, which must be "!".
    [.., "!"] => println!("!!!"),

    // `start` is a slice of everything except the last element, which must be "z".
    [start @ .., "z"] => println!("starts with: {:?}", start),

    // `end` is a slice of everything but the first element, which must be "a".
    ["a", end @ ..] => println!("ends with: {:?}", end),

    // 'whole' is the entire slice and `last` is the final element
    whole @ [.., last] => println!("the last element of {:?} is {}", whole, last),

    rest => println!("{:?}", rest),
}

if let [.., penultimate, _] = slice {
    println!("next to last is {}", penultimate);
}

let tuple = (1, 2, 3, 4, 5);
// The rest pattern may also be used in tuple and tuple
// struct patterns.
match tuple {
    (1, .., y, z) => println!("y={} z={}", y, z),
    (.., 5) => println!("tail must be 5"),
    (..) => println!("matches everything else"),
}
}

范围模式

范围模式匹配其边界定义的范围内的标量值。它们由符号....=)和一侧或两侧的边界组成。

符号左侧的边界称为下界。右侧的边界称为上界

排他范围模式匹配从下界到上界(不包括上界)的所有值。它写为下界,后跟 ..,后跟上界。

例如,模式 'm'..'p' 将只匹配 'm''n''o',特别包括 'p'

包含范围模式匹配从下界到上界(包括上界)的所有值。它写为下界,后跟 ..=,后跟上界。

例如,模式 'm'..='p' 将只匹配值 'm''n''o''p'

从范围模式匹配大于或等于下界的所有值。它写为下界后跟 ..

例如,1.. 将匹配任何大于或等于 1 的整数,如 1、9 或 9001,或 9007199254740991(如果大小合适),但不匹配 0,对于有符号整数不匹配负数。

到排他范围模式匹配小于上界的所有值。它写为 .. 后跟上界。

例如,..10 将匹配任何小于 10 的整数,如 9、1、0,对于有符号整数类型,匹配所有负值。

到包含范围模式匹配小于或等于上界的所有值。它写为 ..= 后跟上界。

例如,..=10 将匹配任何小于或等于 10 的整数,如 10、1、0,对于有符号整数类型,匹配所有负值。

范围模式必须非空;它必须跨越其类型可能值集中至少一个值。换句话说:

  • a..=b 中,必须满足 a ≤ b。例如,范围模式 10..=0 是错误的,但 10..=10 是允许的。
  • a..b 中,必须满足 a < b。例如,范围模式 10..010..10 是错误的。
  • ..b 中,b 不能是其类型的最小值。例如,范围模式 ..-128i8..f64::NEG_INFINITY 是错误的。

边界写为以下之一:

  • 字符、字节、整数或浮点字面量。
  • - 后跟整数或浮点字面量。
  • 路径

Note

我们在语法上接受的比这更多作为 RangePatternBound。我们稍后在语义上拒绝其他内容。

如果边界写为路径,在宏解析之后,路径必须解析为类型为 char、整数类型或浮点类型的常量项。

范围模式匹配其上界和下界的类型,它们必须是相同的类型。

如果边界是路径,边界匹配该类型并具有路径解析到的常量的值。

如果边界是字面量,边界匹配该类型并具有相应字面量表达式的值。

如果边界是前面带有 - 的字面量,边界匹配与相应字面量表达式相同的类型,并具有取反相应字面量表达式值的值。

对于浮点范围模式,常量不能是 NaN

示例:

#![allow(unused)]
fn main() {
let c = 'f';
let valid_variable = match c {
    'a'..='z' => true,
    'A'..='Z' => true,
    'α'..='ω' => true,
    _ => false,
};

let ph = 10;
println!("{}", match ph {
    0..7 => "acid",
    7 => "neutral",
    8..=14 => "base",
    _ => unreachable!(),
});

let uint: u32 = 5;
match uint {
    0 => "zero!",
    1.. => "positive number!",
};

// using paths to constants:
const TROPOSPHERE_MIN : u8 = 6;
const TROPOSPHERE_MAX : u8 = 20;

const STRATOSPHERE_MIN : u8 = TROPOSPHERE_MAX + 1;
const STRATOSPHERE_MAX : u8 = 50;

const MESOSPHERE_MIN : u8 = STRATOSPHERE_MAX + 1;
const MESOSPHERE_MAX : u8 = 85;

let altitude = 70;

println!("{}", match altitude {
    TROPOSPHERE_MIN..=TROPOSPHERE_MAX => "troposphere",
    STRATOSPHERE_MIN..=STRATOSPHERE_MAX => "stratosphere",
    MESOSPHERE_MIN..=MESOSPHERE_MAX => "mesosphere",
    _ => "outer space, maybe",
});

pub mod binary {
    pub const MEGA : u64 = 1024*1024;
    pub const GIGA : u64 = 1024*1024*1024;
}
let n_items = 20_832_425;
let bytes_per_item = 12;
if let size @ binary::MEGA..=binary::GIGA = n_items * bytes_per_item {
    println!("It fits and occupies {} bytes", size);
}

trait MaxValue {
    const MAX: u64;
}
impl MaxValue for u8 {
    const MAX: u64 = (1 << 8) - 1;
}
impl MaxValue for u16 {
    const MAX: u64 = (1 << 16) - 1;
}
impl MaxValue for u32 {
    const MAX: u64 = (1 << 32) - 1;
}
// using qualified paths:
println!("{}", match 0xfacade {
    0 ..= <u8 as MaxValue>::MAX => "fits in a u8",
    0 ..= <u16 as MaxValue>::MAX => "fits in a u16",
    0 ..= <u32 as MaxValue>::MAX => "fits in a u32",
    _ => "too big",
});
}

当范围模式跨越类型的整个可能值集时,固定宽度整数和 char 类型的范围模式是不可反驳的。例如,0u8..=255u8 是不可反驳的。

整数类型的值范围是从其最小值到最大值的闭合范围。

char 类型的值范围正是包含所有 Unicode 标量值的范围:'\u{0000}'..='\u{D7FF}''\u{E000}'..='\u{10FFFF}'

RangeFromPattern 不能用作[切片模式][slice patterns]中子模式的顶级模式。例如,模式 [1.., _] 不是有效模式。

2021 Edition differences

在 2021 版本之前,具有下界和上界的范围模式也可以使用 ... 代替 ..= 编写,具有相同的含义。

引用模式

Syntax
ReferencePattern → ( & | && ) mut? PatternWithoutModernRange

引用模式解引用正在匹配的指针,因此借用它们。

例如,这两个对 x: &i32 的匹配是等效的:

#![allow(unused)]
fn main() {
let int_reference = &3;

let a = match *int_reference { 0 => "zero", _ => "some" };
let b = match int_reference { &0 => "zero", _ => "some" };

assert_eq!(a, b);
}

引用模式的语法产生式必须匹配词法单元 && 以匹配对引用的引用,因为它本身是一个词法单元,而不是两个 & 词法单元。

添加 mut 关键字解引用可变引用。可变性必须与引用的可变性匹配。

引用模式总是不可反驳的。

结构体模式

Syntax
StructPattern
    PathInExpression {
        StructPatternElements?
    }

StructPatternElements
      StructPatternFields ( , | , StructPatternEtCetera )?
    | StructPatternEtCetera

StructPatternFields
    StructPatternField ( , StructPatternField )*

StructPatternField
    OuterAttribute*
    (
        TUPLE_INDEX : Pattern
      | IDENTIFIER : Pattern
      | ref? mut? IDENTIFIER
    )

StructPatternEtCetera..

结构体模式匹配满足其子模式定义的所有标准的结构体、枚举和联合体值。它们也用于[解构][destructure]结构体、枚举或联合体值。

在结构体模式上,字段按名称、索引(对于元组结构体)引用,或通过使用 .. 忽略:

#![allow(unused)]
fn main() {
struct Point {
    x: u32,
    y: u32,
}
let s = Point {x: 1, y: 1};

match s {
    Point {x: 10, y: 20} => (),
    Point {y: 10, x: 20} => (),    // order doesn't matter
    Point {x: 10, ..} => (),
    Point {..} => (),
}

struct PointTuple (
    u32,
    u32,
);
let t = PointTuple(1, 2);

match t {
    PointTuple {0: 10, 1: 20} => (),
    PointTuple {1: 10, 0: 20} => (),   // order doesn't matter
    PointTuple {0: 10, ..} => (),
    PointTuple {..} => (),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
}
let m = Message::Quit;

match m {
    Message::Quit => (),
    Message::Move {x: 10, y: 20} => (),
    Message::Move {..} => (),
}
}

如果未使用 ..,用于匹配结构体的结构体模式需要指定所有字段:

#![allow(unused)]
fn main() {
struct Struct {
   a: i32,
   b: char,
   c: bool,
}
let mut struct_value = Struct{a: 10, b: 'X', c: false};

match struct_value {
    Struct{a: 10, b: 'X', c: false} => (),
    Struct{a: 10, b: 'X', ref c} => (),
    Struct{a: 10, b: 'X', ref mut c} => (),
    Struct{a: 10, b: 'X', c: _} => (),
    Struct{a: _, b: _, c: _} => (),
}
}

用于匹配联合体的结构体模式必须指定恰好一个字段(参见联合体上的模式匹配)。

IDENTIFIER 语法匹配任何值并将其绑定到与给定字段同名的变量。它是 fieldname: fieldname 的简写。refmut 限定符可以包含在内,行为如 patterns.ident.ref 中所述。

#![allow(unused)]
fn main() {
struct Struct {
   a: i32,
   b: char,
   c: bool,
}
let struct_value = Struct{a: 10, b: 'X', c: false};

let Struct { a, b, c } = struct_value;
}

如果 PathInExpression 解析为具有多个变体的枚举的构造函数,或者其子模式之一是可反驳的,则结构体模式是可反驳的。

结构体模式匹配构造函数从类型命名空间中的 PathInExpression 解析的结构体、联合体或枚举变体。有关更多详细信息,请参阅 patterns.tuple-struct.namespace

元组结构体模式

元组结构体模式匹配满足其子模式定义的所有标准的元组结构体和枚举值。它们也用于[解构][destructure]元组结构体或枚举值。

如果 PathInExpression 解析为具有多个变体的枚举的构造函数,或者其子模式之一是可反驳的,则元组结构体模式是可反驳的。

元组结构体模式匹配构造函数从值命名空间中的 PathInExpression 解析的元组结构体或类元组枚举变体

Note

相反,元组结构体或类元组枚举变体的结构体模式,例如 S { 0: _ },匹配构造函数在类型命名空间中解析的元组结构体或变体。

enum E1 { V(u16) }
enum E2 { V(u32) }

// Import `E1::V` from the type namespace only.
mod _0 {
    const V: () = (); // For namespace masking.
    pub(super) use super::E1::*;
}
use _0::*;

// Import `E2::V` from the value namespace only.
mod _1 {
    struct V {} // For namespace masking.
    pub(super) use super::E2::*;
}
use _1::*;

fn f() {
    // This struct pattern matches against the tuple-like
    // enum variant whose constructor was found in the type
    // namespace.
    let V { 0: ..=u16::MAX } = (loop {}) else { loop {} };
    // This tuple struct pattern matches against the tuple-like
    // enum variant whose constructor was found in the value
    // namespace.
    let V(..=u32::MAX) = (loop {}) else { loop {} };
}
// Required due to the odd behavior of `super` within functions.
fn main() {}

Lang 团队已经做出了某些决定,例如在 PR #138458 中,这引发了关于在模式中以这种方式使用值命名空间的可取性的疑问,如 PR #140593 中所述。在代码中不故意依赖此细微差别可能是谨慎的。

元组模式

Syntax
TuplePattern( TuplePatternItems? )

TuplePatternItems
      Pattern ,
    | RestPattern
    | Pattern ( , Pattern )+ ,?

元组模式匹配满足其子模式定义的所有标准的元组值。它们也用于[解构][destructure]元组。

带有单个 RestPattern 的形式 (..) 是一种不需要逗号的特殊形式,匹配任何大小的元组。

当其子模式之一是可反驳的时,元组模式是可反驳的。

使用元组模式的示例:

#![allow(unused)]
fn main() {
let pair = (10, "ten");
let (a, b) = pair;

assert_eq!(a, 10);
assert_eq!(b, "ten");
}

分组模式

Syntax
GroupedPattern( Pattern )

将模式括在括号中可用于显式控制复合模式的优先级。例如,范围模式旁边的引用模式(如 &0..=5)是模糊的,不允许使用,但可以用括号表示。

#![allow(unused)]
fn main() {
let int_reference = &3;
match int_reference {
    &(0..=5) => (),
    _ => (),
}
}

切片模式

Syntax
SlicePattern[ SlicePatternItems? ]

SlicePatternItemsPattern ( , Pattern )* ,?

切片模式可以匹配固定大小的数组和动态大小的切片。

#![allow(unused)]
fn main() {
// Fixed size
let arr = [1, 2, 3];
match arr {
    [1, _, _] => "starts with one",
    [a, b, c] => "starts with something else",
};
}
#![allow(unused)]
fn main() {
// Dynamic size
let v = vec![1, 2, 3];
match v[..] {
    [a, b] => { /* this arm will not apply because the length doesn't match */ }
    [a, b, c] => { /* this arm will apply */ }
    _ => { /* this wildcard is required, since the length is not known statically */ }
};
}

当匹配数组时,只要每个元素都是不可反驳的,切片模式就是不可反驳的。

匹配切片时,只有带有单个 .. [休息模式][rest pattern]或带有 .. 休息模式作为子模式的[标识符模式][identifier pattern]的形式才是不可反驳的。

在切片内,没有下界和上界的范围模式必须用括号括起来,如 (a..),以明确它是要匹配单个切片元素。具有下界和上界的范围模式(如 a..=b)不需要用括号括起来。

路径模式

Syntax
PathPatternPathExpression

_路径模式_是引用常量值或没有字段的结构体或枚举变体的模式。

非限定路径模式可以引用:

  • 枚举变体
  • 结构体
  • 常量
  • 关联常量

限定路径模式只能引用关联常量。

当路径模式引用结构体或枚举变体(枚举只有一个变体)或类型为不可反驳的常量时,它们是不可反驳的。当它们引用可反驳的常量或具有多个变体的枚举的枚举变体时,它们是可反驳的。

常量模式

当类型为 T 的常量 C 用作模式时,我们首先检查 T: PartialEq

此外,我们要求 C 的值具有(递归)结构相等性,其递归定义如下:

  • 整数以及 strboolchar 值始终具有结构相等性。
  • 元组、数组和切片如果其所有字段/元素都具有结构相等性,则具有结构相等性。(特别是,()[] 始终具有结构相等性。)
  • 如果引用指向的值具有结构相等性,则引用具有结构相等性。
  • 如果 structenum 类型的 PartialEq 实例是通过 #[derive(PartialEq)] 派生的,并且所有字段(对于枚举:活动变体的字段)都具有结构相等性,则该类型的值具有结构相等性。
  • 如果原始指针定义为常量整数(然后强制转换/transmute),则具有结构相等性。
  • 如果浮点值不是 NaN,则具有结构相等性。
  • 没有其他内容具有结构相等性。

特别是,C 的值必须在模式构建时(即单态化之前)已知。这意味着涉及泛型参数的关联常量不能用作模式。

C 的值不得包含对可变静态(static mut 项或内部可变 static 项)或 extern 静态的任何引用。

在确保满足所有条件后,常量值被转换为模式,现在其行为就像直接编写了该模式一样。特别是,它完全参与穷举性检查。(对于原始指针,常量是编写此类模式的唯一方式。只有 _ 被认为对这些类型是穷举的。)

或模式

_或模式_是匹配两个或多个子模式之一的模式(例如 A | B | C)。它们可以任意嵌套。在语法上,或模式在允许其他模式的任何地方都被允许(由 Pattern 产生式表示),let 绑定以及函数和闭包参数除外(由 PatternNoTopAlt 产生式表示)。

静态语义

  1. 给定某个深度处的模式 p | q,对于某些任意模式 pq,如果满足以下条件,则该模式被认为是格式错误的:

    • p 推断的类型与为 q 推断的类型不统一,或者
    • pq 中引入的绑定集不相同,或者
    • pq 中同名的任何两个绑定的类型在类型或绑定模式方面不统一。

    类型的统一在所有上述情况下都是精确的,隐式类型强制转换不适用。

  1. 当对表达式 match e_s { a_1 => e_1, ... a_n => e_n } 进行类型检查时,对于包含形式为 p_i | q_i 的模式的每个 match 分支 a_i,如果在其存在的深度 d 处,e_s 的片段类型与 p_i | q_i 不统一,则模式 p_i | q_i 被认为是格式错误的。
  1. 关于穷举性检查,模式 p | q 被认为覆盖 p 以及 q。对于某个构造函数 c(x, ..),分配律适用,使得 c(p | q, ..rest) 覆盖与 c(p, ..rest) | c(q, ..rest) 相同的值集。这可以递归应用,直到没有更多形式为 p | q 的嵌套模式(除了那些存在于顶级的模式)。

    请注意,构造函数我们不是指元组结构体模式,而是指任何积类型的模式。这包括枚举变体、元组结构体、具有命名字段的结构体、数组、元组和切片。

动态语义

  1. 将审查者表达式 e_s 与深度 d 处的模式 c(p | q, ..rest) 进行模式匹配的动态语义(其中 c 是某个构造函数,pq 是任意模式,rest 可选地是 c 中任何剩余的潜在因子)被定义为与 c(p, ..rest) | c(q, ..rest) 相同。

与其他非分隔模式的优先级

如本章其他地方所示,有几种类型的模式在语法上是非分隔的,包括标识符模式、引用模式和或模式。或模式始终具有最低优先级。这允许我们为可能的未来类型归属功能保留语法空间,并减少歧义。例如,x @ A(..) | B(..) 将导致 x 未在所有模式中绑定的错误。&A(x) | B(x) 将导致不同子模式中 x 的类型不匹配。


  1. ObsoleteRangePattern 语法在 2021 版本及以后在语义上无效。

类型系统

类型

Rust 程序中的每个变量、项和值都有一个类型(Type)。的_类型_定义了保存它的内存的解释以及可以对该值执行的操作。

内置类型与语言紧密集成,其方式在用户定义类型中无法模拟。

用户定义类型的能力有限。

类型列表为:

类型表达式

Syntax
Type
      TypeNoBounds
    | ImplTraitType
    | TraitObjectType

TypeNoBounds
      ParenthesizedType
    | ImplTraitTypeOneBound
    | TraitObjectTypeOneBound
    | TypePath
    | TupleType
    | NeverType
    | RawPointerType
    | ReferenceType
    | ArrayType
    | SliceType
    | InferredType
    | QualifiedPathInType
    | BareFunctionType
    | MacroInvocation

上面 Type 语法规则中定义的_类型表达式_是引用类型的语法。它可以引用:

  • 括号 用于消除歧义。
  • 展开为类型表达式。

括号类型

Syntax
ParenthesizedType( Type )

在某些情况下,类型的组合可能是有歧义的。在类型周围使用括号以避免歧义。例如,引用类型中用于类型边界+ 运算符不清楚边界应用于哪里,因此需要使用括号。需要此消除歧义的语法规则使用 TypeNoBounds 规则而不是 Type

#![allow(unused)]
fn main() {
use std::any::Any;
type T<'a> = &'a (dyn Any + Send);
}

递归类型

具名类型 — 结构体枚举联合体 — 可以是递归的。也就是说,每个 enum 变体或 structunion 字段可以直接或间接地引用封闭的 enumstruct 类型本身。

此类递归有限制:

  • 递归类型必须在递归中包含具名类型(不仅仅是类型别名或其他结构类型如数组元组)。因此不允许 type Rec = &'static [Rec]
  • 递归类型的大小必须是有限的;换句话说,类型的递归字段必须是指针类型

递归类型及其使用的示例:

#![allow(unused)]
fn main() {
enum List<T> {
    Nil,
    Cons(T, Box<List<T>>)
}

let a: List<i32> = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil))));
}

布尔类型

#![allow(unused)]
fn main() {
let b: bool = true;
}

布尔类型bool 是一种原始数据类型,可以取两个值之一,称为 truefalse

可以使用关键字 truefalse字面量表达式创建此类型的值,对应于同名的值。

此类型是语言 prelude 的一部分,具有名称 bool

具有布尔类型的对象的大小和对齐均为 1。

值 false 的位模式为 0x00,值 true 的位模式为 0x01。具有布尔类型的对象具有任何其他位模式是未定义行为

布尔类型是各种表达式中许多操作数的类型:

Note

布尔类型的行为类似于但不是枚举类型。在实践中,这主要意味着构造函数不与类型关联(例如 bool::true)。

与所有原始类型一样,布尔类型实现trait CloneCopySizedSendSync

Note

有关库操作,请参阅标准库文档

布尔值上的操作

当对布尔类型的操作数使用某些运算符表达式时,它们使用布尔逻辑的规则进行求值。

逻辑非

b!b
truefalse
falsetrue

逻辑或

aba | b
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

逻辑与

aba & b
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

逻辑异或

aba ^ b
truetruefalse
truefalsetrue
falsetruetrue
falsefalsefalse

比较

aba == b
truetruetrue
truefalsefalse
falsetruefalse
falsefalsetrue
aba > b
truetruefalse
truefalsetrue
falsetruefalse
falsefalsefalse
  • a != b!(a == b) 相同
  • a >= ba == b | a > b 相同
  • a < b!(a >= b) 相同
  • a <= ba == b | a < b 相同

位有效性

bool 的单个字节保证是已初始化的(换句话说,transmute::<bool, u8>(...) 总是健全的 – 但由于某些位模式是无效的 bool,反过来并不总是健全的)。

数值类型

整数类型

无符号整数类型包括:

类型最小值最大值
u8028-1
u160216-1
u320232-1
u640264-1
u12802128-1

有符号二进制补码整数类型包括:

类型最小值最大值
i8-(27)27-1
i16-(215)215-1
i32-(231)231-1
i64-(263)263-1
i128-(2127)2127-1

浮点类型

IEEE 754-2008 “binary32” 和 “binary64” 浮点类型分别是 f32f64

机器相关整数类型

usize 类型是无符号整数类型,其位数与平台的指针类型相同。它可以表示进程中的每个内存地址。

Note

虽然 usize 可以表示每个地址,但将指针转换为 usize 不一定是可逆操作。有关更多信息,请参阅类型转换表达式std::ptr 和[来源]provenance 的文档。

isize 类型是有符号二进制补码整数类型,其位数与平台的指针类型相同。对象和数组大小的理论上限是最大 isize 值。这确保 isize 可用于计算指向对象或数组的指针之间的差异,并且可以寻址对象内的每个字节以及末尾之后的一个字节。

usizeisize 至少为 16 位宽。

Note

许多 Rust 代码可能假定指针、usizeisize 是 32 位或 64 位。因此,16 位指针支持是有限的,可能需要库的明确关注和确认才能支持。

位有效性

对于每种数值类型 TT 的位有效性等同于 [u8; size_of::<T>()] 的位有效性。未初始化的字节不是有效的 u8

字符类型

char 类型表示单个 Unicode 标量值(即,不是代理项的代码点)。

Example

#![allow(unused)]
fn main() {
let c: char = 'a';
let emoji: char = '😀';
let unicode: char = '\u{1F600}';
}

Note

有关 char 类型的实现信息,请参阅标准库文档

char 类型的值表示为 0x0000 到 0xD7FF 或 0xE000 到 0x10FFFF 范围内的 32 位无符号字。创建超出此范围的 char 是立即的未定义行为

char 在所有平台上保证具有与 u32 相同的大小和对齐。

char 的每个字节保证是已初始化的。换句话说,transmute::<char, [u8; size_of::<char>()]>(...) 总是健全的 – 但由于某些位模式是无效的 char,反过来并不总是健全的。

字符串切片类型

字符串切片(str)类型表示字符序列。

#![allow(unused)]
fn main() {
let greeting1: &str = "Hello, world!";
let greeting2: &str = "你好,世界";
}

Note

有关 str 类型的实现信息,请参阅标准库文档

str 类型的值以与 [u8](8 位无符号字节切片)相同的方式表示。

Note

标准库对 str 做了额外的假设:处理 str 的方法假定并确保其包含的数据是有效的 UTF-8。使用非 UTF-8 缓冲区调用 str 方法现在或将来可能导致未定义行为

str动态大小类型。它只能通过指针类型实例化,例如 &str&str 的布局与 &[u8] 的布局相同。

永不类型

Syntax
NeverType!

永不类型 ! 是没有值的类型,表示永不完成的计算的结果。

! 类型的表达式可以被强制转换为任何其他类型。

! 类型只能出现在函数返回类型中,表示它是永不返回的发散函数。

#![allow(unused)]
fn main() {
fn foo() -> ! {
    panic!("This call never returns.");
}
}
#![allow(unused)]
fn main() {
unsafe extern "C" {
    pub safe fn no_return_extern_func() -> !;
}
}

元组类型

Syntax
TupleType
      ( )
    | ( ( Type , )+ Type? )

元组类型是一系列结构类型1,用于其他类型的异构列表。

元组类型的语法是用括号括起来的、逗号分隔的类型列表。

一元组需要在其元素类型后加逗号,以与括号类型消除歧义。

元组类型具有等于类型列表长度的字段数。此字段数决定了元组的元数。具有 n 个字段的元组称为 n 元元组。例如,具有 2 个字段的元组是 2 元元组。

元组的字段使用与其在类型列表中位置匹配的递增数字名称命名。第一个字段是 0。第二个字段是 1。依此类推。每个字段的类型是元组类型列表中相同位置的类型。

为了方便和历史原因,没有字段的元组类型(())通常称为单元单元类型。它的一个值也称为单元单元值

元组类型的一些示例:

  • ()(单元)
  • (i32,)(一元元组)
  • (f64, f64)
  • (String, i32)
  • (i32, String)(与前一个示例不同的类型)
  • (i32, f64, Vec<String>, Option<bool>)

此类型的值使用元组表达式构造。此外,如果没有其他有意义的值可供求值,各种表达式将产生单元值。

可以通过元组索引表达式模式匹配访问元组字段。


  1. 如果内部类型等效,则结构类型始终等效。有关元组的具名版本,请参阅元组结构体

数组类型

Syntax
ArrayType[ Type ; Expression ]

数组是类型为 TN 个元素的固定大小序列。数组类型写为 [T; N]

大小是求值为 usize常量表达式

示例:

#![allow(unused)]
fn main() {
// A stack-allocated array
let array: [i32; 3] = [1, 2, 3];

// A heap-allocated array, coerced to a slice
let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);
}

数组的所有元素始终是已初始化的,并且在安全方法和运算符中对数组的访问始终进行边界检查。

Note

Vec<T> 标准库类型提供堆分配的可调整大小的数组类型。

切片类型

Syntax
SliceType[ Type ]

切片是动态大小类型,表示类型为 T 的元素序列的“视图“。切片类型写为 [T]

切片类型通常通过指针类型使用。例如:

  • &[T]:“共享切片”,通常简称为“切片“。它不拥有它指向的数据;它借用它。
  • &mut [T]:“可变切片”。它可变地借用它指向的数据。
  • Box<[T]>:“装箱切片”

示例:

#![allow(unused)]
fn main() {
// A heap-allocated array, coerced to a slice
let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);

// A (shared) slice into an array
let slice: &[i32] = &boxed_array[..];
}

切片的所有元素始终是已初始化的,并且在安全方法和运算符中对切片的访问始终进行边界检查。

结构体类型

struct 类型是其他类型的异构积,称为该类型的字段1

struct 的新实例可以使用结构体表达式构造。

struct 的内存布局默认未定义,以允许编译器优化(如字段重新排序),但可以使用 repr 属性修复。在这两种情况下,字段可以在相应的结构体表达式中以任何顺序给出;生成的 struct 值将始终具有相同的内存布局。

struct 的字段可以由可见性修饰符限定,以允许在模块外部访问结构体中的数据。

_元组结构体_类型与结构体类型相同,只是字段是匿名的。

_类单元结构体_类型与结构体类型相同,只是它没有字段。由关联的结构体表达式构造的一个值是此类类型的唯一值。


  1. struct 类型类似于 C 中的 struct 类型、ML 系列的记录类型或 Lisp 系列的 struct 类型。

枚举类型

枚举类型是具名的、异构的不相交联合类型,由 enum的名称表示。1

enum声明类型和若干变体,每个变体独立命名,并具有结构体、元组结构体或类单元结构体的语法。

enum 的新实例可以使用结构体表达式构造。

任何 enum 值消耗的内存等于其对应 enum 类型的最大变体的大小,以及存储判别值所需的大小。

枚举类型不能结构化地表示为类型,而必须通过命名引用 enum来表示。


  1. enum 类型类似于 Haskell 中的 data 构造函数声明或 Limbo 中的 pick ADT

联合体类型

联合体类型是具名的、异构的类 C 联合体,由 union的名称表示。

联合体没有“活动字段“的概念。相反,每次联合体访问都将联合体内容的一部分转换为访问字段的类型。

由于转换可能导致意外或未定义行为,从联合体字段读取需要 unsafe

联合体字段类型也仅限于确保它们永远不需要丢弃的类型子集。有关更多详细信息,请参阅文档。

union 的内存布局默认未定义(特别是字段必须在偏移量 0 处),但 #[repr(...)] 属性可用于修复布局。

函数项类型

当被引用时,函数项或类元组结构体或枚举变体的构造函数产生其_函数项类型_的零大小值。

该类型显式标识函数 - 其名称、其类型参数及其早期绑定的生命周期参数(但不包括其晚期绑定的生命周期参数,这些参数仅在调用函数时分配) - 因此该值不需要包含实际的函数指针,并且在调用函数时不需要间接寻址。

没有直接引用函数项类型的语法,但编译器将在错误消息中将该类型显示为类似 fn(u32) -> i32 {fn_name} 的内容。

由于函数项类型显式标识函数,不同函数的项类型 - 不同项或具有不同泛型的相同项 - 是不同的,混合它们将创建类型错误:

#![allow(unused)]
fn main() {
fn foo<T>() { }
let x = &mut foo::<i32>;
*x = foo::<u32>; //~ ERROR mismatched types
}

但是,存在从函数项到具有相同签名的函数指针强制转换,这不仅在直接期望函数指针时使用函数项时触发,而且在具有相同签名的不同函数项类型在同一 ifmatch 的不同分支中相遇时也会触发:

#![allow(unused)]
fn main() {
let want_i32 = false;
fn foo<T>() { }

// `foo_ptr_1` has function pointer type `fn()` here
let foo_ptr_1: fn() = foo::<i32>;

// ... and so does `foo_ptr_2` - this type-checks.
let foo_ptr_2 = if want_i32 {
    foo::<i32>
} else {
    foo::<u32>
};
}

所有函数项都实现 CopyCloneSendSync

除非函数具有以下任何特征,否则会实现 FnFnMutFnOnce

闭包类型

闭包表达式产生具有唯一匿名类型的闭包值,该类型无法写出。闭包类型大致等同于包含捕获值的结构体。例如,以下闭包:

#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Point { x: i32, y: i32 }
struct Rectangle { left_top: Point, right_bottom: Point }

fn f<F : FnOnce() -> String> (g: F) {
    println!("{}", g());
}

let mut rect = Rectangle {
    left_top: Point { x: 1, y: 1 },
    right_bottom: Point { x: 0, y: 0 }
};

let c = || {
    rect.left_top.x += 1;
    rect.right_bottom.x += 1;
    format!("{:?}", rect.left_top)
};
f(c); // Prints "Point { x: 2, y: 1 }".
}

生成大致如下所示的闭包类型:

// Note: This is not exactly how it is translated, this is only for
// illustration.

struct Closure<'a> {
    left_top : &'a mut Point,
    right_bottom_x : &'a mut i32,
}

impl<'a> FnOnce<()> for Closure<'a> {
    type Output = String;
    extern "rust-call" fn call_once(self, args: ()) -> String {
        self.left_top.x += 1;
        *self.right_bottom_x += 1;
        format!("{:?}", self.left_top)
    }
}

以便对 f 的调用如下工作:

f(Closure{ left_top: &mut rect.left_top, right_bottom_x: &mut rect.right_bottom.x });

捕获模式

捕获模式决定如何将环境中的位置表达式借用或移动到闭包中。捕获模式是:

  1. 不可变借用(ImmBorrow) — 位置表达式作为共享引用被捕获。
  2. 唯一不可变借用(UniqueImmBorrow) — 这类似于不可变借用,但必须是唯一的,如下面所述。
  3. 可变借用(MutBorrow) — 位置表达式作为可变引用被捕获。
  4. 移动(ByValue) — 位置表达式通过移动值到闭包中被捕获。

环境中的位置表达式从与闭包主体内捕获值的使用方式兼容的第一个模式被捕获。该模式不受闭包周围代码的影响,例如涉及变量或字段的生命周期,或闭包本身的生命周期。

Copy

实现 Copy 并被移动到闭包中的值使用 ImmBorrow 模式捕获。

#![allow(unused)]
fn main() {
let x = [0; 1024];
let c = || {
    let y = x; // x captured by ImmBorrow
};
}

异步输入捕获

异步闭包始终捕获所有输入参数,无论它们是否在主体中使用。

捕获精度

捕获路径是从环境中的变量开始的序列,后跟零个或多个从该变量的位置投影。

位置投影是应用于变量的字段访问元组索引解引用(和自动解引用)、数组或切片索引表达式或模式解构

Note

rustc 中,模式解构被脱糖为一系列解引用和字段或元素访问。

闭包借用或移动捕获路径,该路径可能会根据下面描述的规则被截断。

例如:

#![allow(unused)]
fn main() {
struct SomeStruct {
    f1: (i32, i32),
}
let s = SomeStruct { f1: (1, 2) };

let c = || {
    let x = s.f1.1; // s.f1.1 captured by ImmBorrow
};
c();
}

这里捕获路径是局部变量 s,后跟字段访问 .f1,然后是元组索引 .1。此闭包捕获 s.f1.1 的不可变借用。

共享前缀

在捕获路径和该路径的祖先之一都被闭包捕获的情况下,祖先路径使用两个捕获中的最高捕获模式被捕获,CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode),使用严格弱排序:

ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue

请注意,这可能需要递归应用。

#![allow(unused)]
fn main() {
// In this example, there are three different capture paths with a shared ancestor:
fn move_value<T>(_: T){}
let s = String::from("S");
let t = (s, String::from("T"));
let mut u = (t, String::from("U"));

let c = || {
    println!("{:?}", u); // u captured by ImmBorrow
    u.1.truncate(0); // u.1 captured by MutBorrow
    move_value(u.0.0); // u.0.0 captured by ByValue
};
c();
}

总体而言,此闭包将通过 ByValue 捕获 u

最右共享引用截断

如果解引用应用于共享引用,则捕获路径在捕获路径中最右边的解引用处截断。

允许此截断是因为通过共享引用读取的字段将始终通过共享引用或复制读取。当额外的精度从借用检查的角度来看没有任何好处时,这有助于减少捕获的大小。

它是最右边解引用的原因是为了帮助避免比必要更短的生命周期。考虑以下示例:

#![allow(unused)]
fn main() {
struct Int(i32);
struct B<'a>(&'a i32);

struct MyStruct<'a> {
   a: &'static Int,
   b: B<'a>,
}

fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static {
    let c = || drop(&m.a.0);
    c
}
}

如果这要捕获 m,那么闭包将不再比 'static 活得更久,因为 m 被约束到 'a。相反,它通过 ImmBorrow 捕获 (*(*m).a)

通配符模式绑定

闭包只捕获需要读取的数据。使用通配符模式绑定值不会读取值,因此不会捕获该位置。

#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let x = S;
let c = || {
    let _ = x;  // Does not capture `x`.
};
let c = || match x {
    _ => (), // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
}

解构元组、结构体和单变体枚举本身不会导致读取或捕获位置。

Note

标记了 #[non_exhaustive] 的枚举始终被视为具有多个变体。参见 type.closure.capture.precision.discriminants.non_exhaustive

#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.

// Destructuring tuples does not cause a read or capture.
let x = (S,);
let c = || {
    let (..) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();

// Destructuring unit structs does not cause a read or capture.
let x = S;
let c = || {
    let S = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();

// Destructuring structs does not cause a read or capture.
struct W<T>(T);
let x = W(S);
let c = || {
    let W(..) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();

// Destructuring single-variant enums does not cause a read
// or capture.
enum E<T> { V(T) }
let x = E::V(S);
let c = || {
    let E::V(..) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
}

RestPattern..)或 StructPatternEtCetera(也是 ..)匹配的字段不会被读取,并且这些字段不会被捕获。

#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let x = (S, S);
let c = || {
    let (x0, ..) = x;  // Captures `x.0` by `ByValue`.
};
// Only the first tuple field was captured by the closure.
x.1; // OK: `x.1` can be moved here.
c();
}

不支持数组和切片的部分捕获;即使使用通配符模式匹配、索引或子切片,也始终捕获整个切片或数组。

#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let mut x = [S, S];
let c = || {
    let [x0, _] = x; // Captures all of `x` by `ByValue`.
};
let _ = &mut x[1]; // ERROR: Borrow of moved value.
}

使用通配符匹配的值仍然必须被初始化。

#![allow(unused)]
fn main() {
let x: u8;
let c = || {
    let _ = x; // ERROR: Binding `x` isn't initialized.
};
}

判别值读取的捕获

如果模式匹配读取判别值,则包含该判别值的位置通过 ImmBorrow 被捕获。

匹配具有多个变体的枚举的变体会读取判别值,通过 ImmBorrow 捕获该位置。

#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let mut x = (Some(S), S);
let c = || match x {
    (None, _) => (),
//   ^^^^
// This pattern requires reading the discriminant, which
// causes `x.0` to be captured by `ImmBorrow`.
    _ => (),
};
let _ = &mut x.0; // ERROR: Cannot borrow `x.0` as mutable.
//           ^^^
// The closure is still live, so `x.0` is still immutably
// borrowed here.
c();
}
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let x = (Some(S), S);
let c = || match x { // Captures `x.0` by `ImmBorrow`.
    (None, _) => (),
    _ => (),
};
// Though `x.0` is captured due to the discriminant read,
// `x.1` is not captured.
x.1; // OK: `x.1` can be moved here.
c();
}

匹配单变体枚举的唯一变体不会读取判别值,也不会捕获该位置。

#![allow(unused)]
fn main() {
enum E<T> { V(T) } // A single-variant enum.
let x = E::V(());
let c = || {
    let E::V(_) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
}

如果 #[non_exhaustive] 应用于枚举,则为了决定是否发生读取,该枚举被视为具有多个变体,即使它实际上只有一个变体。

即使除了正在匹配的变体之外的所有变体都是无人居住的,使得模式是不可反驳的,如果判别值在其他情况下会被读取,则仍然会被读取。

#![allow(unused)]
fn main() {
enum Empty {}
let mut x = Ok::<_, Empty>(42);
let c = || {
    let Ok(_) = x; // Captures `x` by `ImmBorrow`.
};
let _ = &mut x; // ERROR: Cannot borrow `x` as mutable.
c();
}

捕获和范围模式

匹配[范围模式]range pattern会读取被匹配的位置,即使该范围包含类型的所有可能值,并通过 ImmBorrow 捕获该位置。

#![allow(unused)]
fn main() {
let mut x = 0u8;
let c = || {
    let 0..=u8::MAX = x; // Captures `x` by `ImmBorrow`.
};
let _ = &mut x; // ERROR: Cannot borrow `x` as mutable.
c();
}

捕获和切片模式

将切片与[切片模式]slice pattern匹配(不是仅具有单个[休息模式]rest pattern的模式(即 [..]))被视为对切片长度的读取,并通过 ImmBorrow 捕获切片。

#![allow(unused)]
fn main() {
let x: &mut [u8] = &mut [];
let c = || match x { // Captures `*x` by `ImmBorrow`.
    &mut [] => (),
//       ^^
// This matches a slice of exactly zero elements. To know whether the
// scrutinee matches, the length must be read, causing the slice to
// be captured.
    _ => (),
};
let _ = &mut *x; // ERROR: Cannot borrow `*x` as mutable.
c();
}
#![allow(unused)]
fn main() {
let x: &mut [u8] = &mut [];
let c = || match x { // Does not capture `*x`.
    [..] => (),
//   ^^ Rest pattern.
};
let _ = &mut *x; // OK: `*x` can be borrow here.
c();
}

Note

也许令人惊讶的是,即使长度包含在(宽)指针中,被读取和被捕获的是被指向者(切片)的位置。

#![allow(unused)]
fn main() {
fn f<'l: 's, 's>(x: &'s mut &'l [u8]) -> impl Fn() + 'l {
    // The closure outlives `'l` because it captures `**x`. If
    // instead it captured `*x`, it would not live long enough
    // to satisfy the `impl Fn() + 'l` bound.
    || match *x { // Captures `**x` by `ImmBorrow`.
        &[] => (),
        _ => (),
    }
}
}

通过这种方式,行为与在审查者中解引用到切片是一致的。

#![allow(unused)]
fn main() {
fn f<'l: 's, 's>(x: &'s mut &'l [u8]) -> impl Fn() + 'l {
    || match **x { // Captures `**x` by `ImmBorrow`.
        [] => (),
        _ => (),
    }
}
}

有关详细信息,请参阅 Rust PR #138961

由于数组的长度由其类型固定,将数组与切片模式匹配本身不会捕获该位置。

#![allow(unused)]
fn main() {
let x: [u8; 1] = [0];
let c = || match x { // Does not capture `x`.
    [_] => (), // Length is fixed.
};
x; // OK: `x` can be moved here.
c();
}

在移动上下文中捕获引用

因为不允许将字段移出引用,move 闭包只会捕获捕获路径中运行到但不包括第一次解引用引用的前缀。引用本身将被移动到闭包中。

#![allow(unused)]
fn main() {
struct T(String, String);

let mut t = T(String::from("foo"), String::from("bar"));
let t_mut_ref = &mut t;
let mut c = move || {
    t_mut_ref.0.push_str("123"); // captures `t_mut_ref` ByValue
};
c();
}

裸指针解引用

因为解引用裸指针是 unsafe 的,闭包只会捕获捕获路径中运行到但不包括第一次解引用裸指针的前缀。

#![allow(unused)]
fn main() {
struct T(String, String);

let t = T(String::from("foo"), String::from("bar"));
let t_ptr = &t as *const T;

let c = || unsafe {
    println!("{}", (*t_ptr).0); // captures `t_ptr` by ImmBorrow
};
c();
}

联合体字段

因为访问联合体字段是 unsafe 的,闭包只会捕获捕获路径中运行到联合体本身的前缀。

#![allow(unused)]
fn main() {
union U {
    a: (i32, i32),
    b: bool,
}
let u = U { a: (123, 456) };

let c = || {
    let x = unsafe { u.a.0 }; // captures `u` ByValue
};
c();

// This also includes writing to fields.
let mut u = U { a: (123, 456) };

let mut c = || {
    u.b = true; // captures `u` with MutBorrow
};
c();
}

引用到未对齐的 struct

因为创建对结构中未对齐字段的引用是未定义行为,闭包只会捕获捕获路径中运行到但不包括第一次使用 packed 表示的结构中的字段访问的前缀。这包括所有字段,即使是那些对齐的字段,以防止结构中的任何字段将来更改时的兼容性问题。

#![allow(unused)]
fn main() {
#[repr(packed)]
struct T(i32, i32);

let t = T(2, 5);
let c = || {
    let a = t.0; // captures `t` with ImmBorrow
};
// Copies out of `t` are ok.
let (a, b) = (t.0, t.1);
c();
}

类似地,获取未对齐字段的地址也会捕获整个结构体:

#![allow(unused)]
fn main() {
#[repr(packed)]
struct T(String, String);

let mut t = T(String::new(), String::new());
let c = || {
    let a = std::ptr::addr_of!(t.1); // captures `t` with ImmBorrow
};
let a = t.0; // ERROR: cannot move out of `t.0` because it is borrowed
c();
}

但如果它不是 packed 的,则上面的代码有效,因为它精确地捕获了字段:

#![allow(unused)]
fn main() {
struct T(String, String);

let mut t = T(String::new(), String::new());
let c = || {
    let a = std::ptr::addr_of!(t.1); // captures `t.1` with ImmBorrow
};
// The move here is allowed.
let a = t.0;
c();
}

Box 与其他 Deref 实现

BoxDeref trait 实现与其他 Deref 实现的处理方式不同,因为它被视为特殊实体。

例如,让我们看涉及 RcBox 的示例。*rc 被脱糖为对 Rc 上定义的 trait 方法 deref 的调用,但由于 *box 被不同对待,因此可以对 Box 的内容进行精确捕获。

move 闭包中的 Box

在非 move 闭包中,如果 Box 的内容未被移入闭包主体,则 Box 的内容被精确捕获。

#![allow(unused)]
fn main() {
struct S(String);

let b = Box::new(S(String::new()));
let c_box = || {
    let x = &(*b).0; // captures `(*b).0` by ImmBorrow
};
c_box();

// Contrast `Box` with another type that implements Deref:
let r = std::rc::Rc::new(S(String::new()));
let c_rc = || {
    let x = &(*r).0; // captures `r` by ImmBorrow
};
c_rc();
}

然而,如果 Box 的内容被移入闭包,则 box 被完全捕获。这样做是为了最小化需要移入闭包的数据量。

#![allow(unused)]
fn main() {
// This is the same as the example above except the closure
// moves the value instead of taking a reference to it.

struct S(String);

let b = Box::new(S(String::new()));
let c_box = || {
    let x = (*b).0; // captures `b` with ByValue
};
c_box();
}

move 闭包中的 Box

类似于在非 move 闭包中移动 Box 的内容,在 move 闭包中读取 Box 的内容将完全捕获 Box

#![allow(unused)]
fn main() {
struct S(i32);

let b = Box::new(S(10));
let c_box = move || {
    let x = (*b).0; // captures `b` with ByValue
};
}

捕获中的唯一不可变借用

捕获可以通过一种特殊类型的借用(称为_唯一不可变借用_)发生,该借用不能在语言中的任何其他地方使用,也不能显式写出。当修改可变引用的被引用者时会发生这种情况,如以下示例所示:

#![allow(unused)]
fn main() {
let mut b = false;
let x = &mut b;
let mut c = || {
    // An ImmBorrow and a MutBorrow of `x`.
    let a = &x;
    *x = true; // `x` captured by UniqueImmBorrow
};
// The following line is an error:
// let y = &x;
c();
// However, the following is OK.
let z = &x;
}

在这种情况下,不能可变地借用 x,因为 x 不是 mut。但同时,不可变地借用 x 会使赋值非法,因为 & &mut 引用可能不是唯一的,因此不能安全地用于修改值。所以使用了唯一不可变借用:它不可变地借用 x,但像可变借用一样,它必须是唯一的。

在上面的示例中,取消注释 y 的声明将产生错误,因为它会违反闭包对 x 的借用的唯一性;z 的声明是有效的,因为闭包的生命周期在块结束时已过期,释放了借用。

调用 trait 和强制转换

闭包类型都实现 FnOnce,表示它们可以通过消耗闭包的所有权来调用一次。此外,一些闭包实现更具体的调用 trait:

  • 不从任何捕获的变量移出的闭包实现 FnMut,表示它可以通过可变引用调用。
  • 不修改或从任何捕获的变量移出的闭包实现 Fn,表示它可以通过共享引用调用。

Note

move 闭包仍然可以实现 FnFnMut,即使它们通过移动捕获变量。这是因为闭包类型实现的 trait 由闭包对捕获值的操作决定,而不是由它如何捕获它们决定。

非捕获闭包是不从其环境捕获任何内容的闭包。非异步、非捕获闭包可以被强制转换为具有匹配签名的函数指针(例如 fn())。

#![allow(unused)]
fn main() {
let add = |x, y| x + y;

let mut x = add(5,7);

type Binop = fn(i32, i32) -> i32;
let bo: Binop = add;
x = bo(5,7);
}

异步闭包 trait

异步闭包对是否实现 FnMutFn 有进一步的限制。

异步闭包返回的 Future 具有与闭包类似的捕获特性。它根据异步闭包中位置表达式的使用方式从异步闭包中捕获它们。如果异步闭包具有以下任一属性,则称其借出给其 Future

  • Future 包含可变捕获。
  • 异步闭包按值捕获,除非值通过解引用投影访问。

如果异步闭包借出给其 Future,则FnMutFn 不会被实现。FnOnce 始终被实现。

示例:可变捕获的第一个子句可以通过以下方式说明:

#![allow(unused)]
fn main() {
fn takes_callback<Fut: Future>(c: impl FnMut() -> Fut) {}

fn f() {
    let mut x = 1i32;
    let c = async || {
        x = 2;  // x captured with MutBorrow
    };
    takes_callback(c);  // ERROR: async closure does not implement `FnMut`
}
}

常规值捕获的第二个子句可以通过以下方式说明:

#![allow(unused)]
fn main() {
fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {}

fn f() {
    let x = &1i32;
    let c = async move || {
        let a = x + 2;  // x captured ByValue
    };
    takes_callback(c);  // ERROR: async closure does not implement `Fn`
}
}

第二个子句的例外可以通过使用解引用来说明,这确实允许实现 FnFnMut

#![allow(unused)]
fn main() {
fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {}

fn f() {
    let x = &1i32;
    let c = async move || {
        let a = *x + 2;
    };
    takes_callback(c);  // OK: implements `Fn`
}
}

异步闭包实现 AsyncFnAsyncFnMutAsyncFnOnce 的方式类似于常规闭包实现 FnFnMutFnOnce;也就是说,取决于其主体中捕获变量的使用。

其他 trait

所有闭包类型都实现 Sized。此外,如果其存储的捕获类型允许,闭包类型还实现以下 trait:

SendSync 的规则与普通结构体类型的规则匹配,而 CloneCopy 的行为就像派生的一样。对于 Clone,捕获值的克隆顺序未指定。

因为捕获通常是通过引用进行的,所以会出现以下一般规则:

  • 如果所有捕获的值都是 Sync,则闭包是 Sync 的。
  • 如果所有通过非唯一不可变引用捕获的值都是 Sync,并且所有通过唯一不可变或可变引用、复制或移动捕获的值都是 Send,则闭包是 Send 的。
  • 如果它不通过唯一不可变或可变引用捕获任何值,并且如果它通过复制或移动捕获的所有值分别是 CloneCopy,则闭包是 CloneCopy 的。

丢弃顺序

如果闭包按值捕获复合类型(如结构体、元组和枚举)的字段,则该字段的生命周期现在将绑定到闭包。因此,复合类型的不相交字段可能在不同时间被丢弃。

#![allow(unused)]
fn main() {
{
    let tuple =
      (String::from("foo"), String::from("bar")); // --+
    { //                                               |
        let c = || { // ----------------------------+  |
            // tuple.0 is captured into the closure |  |
            drop(tuple.0); //                       |  |
        }; //                                       |  |
    } // 'c' and 'tuple.0' dropped here ------------+  |
} // tuple.1 dropped here -----------------------------+
}

2018 版本及之前

闭包类型差异

在 2018 版本及之前,闭包始终捕获整个变量,没有其精确的捕获路径。这意味着对于闭包类型部分中使用的示例,生成的闭包类型将如下所示:

struct Closure<'a> {
    rect : &'a mut Rectangle,
}

impl<'a> FnOnce<()> for Closure<'a> {
    type Output = String;
    extern "rust-call" fn call_once(self, args: ()) -> String {
        self.rect.left_top.x += 1;
        self.rect.right_bottom.x += 1;
        format!("{:?}", self.rect.left_top)
    }
}

f 的调用将如下工作:

f(Closure { rect: rect });

捕获精度差异

复合类型(如结构体、元组和枚举)始终被完整捕获,而不是按单个字段捕获。因此,可能需要借用到局部变量以捕获单个字段:

#![allow(unused)]
fn main() {
use std::collections::HashSet;

struct SetVec {
    set: HashSet<u32>,
    vec: Vec<u32>
}

impl SetVec {
    fn populate(&mut self) {
        let vec = &mut self.vec;
        self.set.iter().for_each(|&n| {
            vec.push(n);
        })
    }
}
}

如果闭包直接使用 self.vec,那么它将尝试通过可变引用捕获 self。但由于 self.set 已经被借用以进行迭代,代码将无法编译。

如果使用 move 关键字,则所有捕获都是通过移动,或者对于 Copy 类型,通过复制,无论借用是否有效。move 关键字通常用于允许闭包比捕获的值活得更久,例如如果闭包被返回或用于生成新线程。

无论数据是否会被闭包读取,即在通配符模式的情况下,如果在闭包内提到在闭包外部定义的变量,则该变量将被完整捕获。

丢弃顺序差异

由于复合类型被完整捕获,按值捕获这些复合类型之一的闭包将在闭包被丢弃时同时丢弃整个捕获的变量。

#![allow(unused)]
fn main() {
{
    let tuple =
      (String::from("foo"), String::from("bar"));
    {
        let c = || { // --------------------------+
            // tuple is captured into the closure |
            drop(tuple.0); //                     |
        }; //                                     |
    } // 'c' and 'tuple' dropped here ------------+
}
}

指针类型

所有指针都是显式的一等值。它们可以被移动或复制,存储在数据结构中,并从函数返回。

引用(&&mut

Syntax
ReferenceType& Lifetime? mut? TypeNoBounds

共享引用(&

共享引用指向由某个其他值拥有的内存。

当创建对值的共享引用时,它会阻止值的直接修改。内部可变性在某些情况下为此提供了例外。顾名思义,可以存在任意数量的共享引用。共享引用类型写为 &type,当需要指定显式生命周期时写为 &'a type

复制引用是“浅层“操作:它只涉及复制指针本身,也就是说,指针是 Copy 的。释放引用对其指向的值没有影响,但引用临时值将在引用本身的作用域内保持其存活。

可变引用(&mut

可变引用指向由某个其他值拥有的内存。可变引用类型写为 &mut type&'a mut type

可变引用(未被借用的)是访问其指向的值的唯一方式,因此不是 Copy 的。

裸指针(*const*mut

Syntax
RawPointerType* ( mut | const ) TypeNoBounds

裸指针是没有安全性或活性保证的指针。裸指针写为 *const T*mut T。例如 *const i32 表示指向 32 位整数的裸指针。

复制或丢弃裸指针对任何其他值的生命周期没有影响。

解引用裸指针是[unsafe 操作]。

这也可以用于通过重新借用(&*&mut *)将裸指针转换为引用。通常不鼓励使用裸指针;它们的存在是为了支持与外部代码的互操作性,以及编写性能关键或低级函数。

比较裸指针时,它们按其地址进行比较,而不是按它们指向的内容进行比较。比较指向动态大小类型的裸指针时,还会比较它们的附加数据。

裸指针可以直接使用 &raw const 创建 *const 指针,使用 &raw mut 创建 *mut 指针。

智能指针

标准库包含除引用和裸指针之外的额外“智能指针“类型。

位有效性

尽管指针和引用在大多数平台上发出的机器代码中类似于 usize,但将引用或指针类型转换为非指针类型的语义目前尚未确定。因此,将指针或引用类型 P 转换为 [u8; size_of::<P>()] 可能无效。

对于瘦裸指针(即,对于 T: SizedP = *const TP = *mut T),反向方向(从整数或整数数组转换为 P)始终有效。但是,通过此类转换产生的指针可能无法被解引用(即使 T 具有零大小)。

函数指针类型

函数指针类型使用 fn 关键字编写,引用标识在编译时不一定已知的函数。

Binop 定义为函数指针类型的示例:

#![allow(unused)]
fn main() {
fn add(x: i32, y: i32) -> i32 {
    x + y
}

let mut x = add(5,7);

type Binop = fn(i32, i32) -> i32;
let bo: Binop = add;
x = bo(5,7);
}

函数指针可以通过从函数项和非捕获、非异步闭包的强制转换创建。

unsafe 限定符表示该类型的值是不安全函数extern 限定符表示它是外部函数

要使函数成为可变参数,其 extern ABI 必须是 items.extern.variadic.conventions 中列出的那些之一。

函数指针参数上的属性

函数指针参数上的属性遵循与常规函数参数相同的规则和限制。

Trait 对象

Syntax
TraitObjectTypedyn? Bounds

TraitObjectTypeOneBounddyn? TraitBound

Trait 对象是实现一组 trait 的另一个类型的不透明值。该组 trait 由 dyn 兼容基础 trait 加上任意数量的自动 trait组成。

Trait 对象实现基础 trait、其自动 trait 以及基础 trait 的任何超级 trait

Trait 对象写为关键字 dyn 后跟一组 trait 约束,但对 trait 约束有以下限制。

不能有多个非自动 trait,不能有多个生命周期,并且不允许选择退出约束(例如 ?Sized)。此外,到 trait 的路径可以用括号括起来。

例如,给定一个 trait Trait,以下都是 trait 对象:

  • dyn Trait
  • dyn Trait + Send
  • dyn Trait + Send + Sync
  • dyn Trait + 'static
  • dyn Trait + Send + 'static
  • dyn Trait +
  • dyn 'static + Trait.
  • dyn (Trait)

2021 Edition differences

在 2021 版本之前,可以省略 dyn 关键字。

2018 Edition differences

在 2015 版本中,如果 trait 对象的第一个约束是以 :: 开头的路径,则 dyn 将被视为路径的一部分。第一个路径可以放在括号中以解决此问题。因此,如果您想要具有 trait ::your_module::Trait 的 trait 对象,应将其写为 dyn (::your_module::Trait)

从 2018 版本开始,dyn 是真正的关键字,不允许在路径中使用,因此不需要括号。

如果基础 trait 相互别名,自动 trait 集合相同且生命周期约束相同,则两个 trait 对象类型相互别名。例如,dyn Trait + Send + UnwindSafedyn Trait + UnwindSafe + Send 相同。

由于值的具体类型是不透明的,trait 对象是动态大小类型。与所有 DST 一样,trait 对象在某种指针类型后面使用;例如 &dyn SomeTraitBox<dyn SomeTrait>。指向 trait 对象的指针的每个实例包括:

  • 指向实现 SomeTrait 的类型 T 的实例的指针
  • 虚拟方法表,通常简称为 vtable,其中包含 T 实现的 SomeTrait 及其超级 trait的每个方法的指向 T 实现的指针(即函数指针)。

Trait 对象的目的是允许方法的“后期绑定“。在 trait 对象上调用方法会导致运行时的虚拟分派:即,从 trait 对象 vtable 加载函数指针并间接调用。每个 vtable 条目的实际实现可以因对象而异。

Trait 对象的示例:

trait Printable {
    fn stringify(&self) -> String;
}

impl Printable for i32 {
    fn stringify(&self) -> String { self.to_string() }
}

fn print(a: Box<dyn Printable>) {
    println!("{}", a.stringify());
}

fn main() {
    print(Box::new(10) as Box<dyn Printable>);
}

在此示例中,trait Printableprint 的类型签名和 main 中的强制转换表达式中都作为 trait 对象出现。

Trait 对象生命周期约束

由于 trait 对象可以包含引用,这些引用的生命周期需要作为 trait 对象的一部分表达。此生命周期写为 Trait + 'a。有默认值允许此生命周期通常可以通过合理的选择推断。

Impl trait

Syntax
ImplTraitTypeimpl Bounds

ImplTraitTypeOneBoundimpl TraitBound

impl Trait 提供了指定实现特定 trait 的未命名但具体类型的方法。它可以出现在两种位置:参数位置(它可以充当函数的匿名类型参数)和返回位置(它可以充当抽象返回类型)。

#![allow(unused)]
fn main() {
trait Trait {}
impl Trait for () {}

// argument position: anonymous type parameter
fn foo(arg: impl Trait) {
}

// return position: abstract return type
fn bar() -> impl Trait {
}
}

匿名类型参数

Note

这通常被称为“参数位置的 impl Trait“。(术语“参数“在这里更正确,但“参数位置的 impl Trait“是此功能开发期间使用的措辞,并且在实现的部分中仍然存在。)

函数可以使用 impl 后跟一组 trait 约束来声明参数具有匿名类型。调用者必须提供满足匿名类型参数声明的约束的类型,并且函数只能使用通过匿名类型参数的 trait 约束可用的方法。

例如,这两种形式几乎等效:

#![allow(unused)]
fn main() {
trait Trait {}

// generic type parameter
fn with_generic_type<T: Trait>(arg: T) {
}

// impl Trait in argument position
fn with_impl_trait(arg: impl Trait) {
}
}

也就是说,参数位置的 impl Trait 是泛型类型参数(如 <T: Trait>)的语法糖,只是该类型是匿名的,不出现在 GenericParams 列表中。

Note

对于函数参数,泛型类型参数和 impl Trait 并不完全等效。使用泛型参数(如 <T: Trait>),调用者可以选择使用 GenericArgs 在调用站点显式指定 T 的泛型参数,例如 foo::<usize>(1)。将参数从一种更改为另一种可能构成对函数调用者的破坏性更改,因为这会更改泛型参数的数量。

抽象返回类型

Note

这通常被称为“返回位置的 impl Trait“。

函数可以使用 impl Trait 返回抽象返回类型。这些类型代表另一个具体类型,调用者只能使用指定的 Trait 声明的方法。

函数的每个可能返回值必须解析为相同的具体类型。

返回位置的 impl Trait 允许函数返回未装箱的抽象类型。这在闭包和迭代器中特别有用。例如,闭包具有唯一的、不可写的类型。以前,从函数返回闭包的唯一方法是使用 trait 对象

#![allow(unused)]
fn main() {
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}
}

这可能会因堆分配和动态分派而产生性能损失。无法完全指定闭包的类型,只能使用 Fn trait。这意味着 trait 对象是必要的。然而,使用 impl Trait,可以更简单地编写:

#![allow(unused)]
fn main() {
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}
}

这也避免了使用装箱 trait 对象的缺点。

类似地,迭代器的具体类型可能变得非常复杂,包含链中所有先前迭代器的类型。返回 impl Iterator 意味着函数仅将 Iterator trait 暴露为其返回类型的约束,而不是显式指定涉及的所有其他迭代器类型。

Trait 和 trait 实现中的返回位置 impl Trait

Trait 中的函数也可以使用 impl Trait 作为匿名关联类型的语法。

Trait 中关联函数返回类型中的每个 impl Trait 都被脱糖为匿名关联类型。实现函数签名中出现的返回类型用于确定关联类型的值。

捕获

每个返回位置 impl Trait 抽象类型的背后是某个隐藏的具体类型。为了使此具体类型使用泛型参数,该泛型参数必须被抽象类型捕获

自动捕获

返回位置 impl Trait 抽象类型自动捕获所有在作用域内的泛型参数,包括泛型类型、const 和生命周期参数(包括高阶参数)。

2024 Edition differences

在 2024 版本之前,在自由函数以及固有实现的关联函数和方法上,未出现在抽象返回类型约束中的泛型生命周期参数不会被自动捕获。

精确捕获

返回位置 impl Trait 抽象类型捕获的泛型参数集可以使用 use<..> 约束显式控制。如果存在,则只有列在 use<..> 约束中的泛型参数会被捕获。例如:

#![allow(unused)]
fn main() {
fn capture<'a, 'b, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
  //                                      ~~~~~~~~~~~~~~~~~~~~~~~
  //                                     Captures `'a` and `T` only.
  (x, y)
}
}

目前,约束列表中只能存在一个 use<..> 约束,必须包含所有在作用域内的类型和 const 泛型参数,并且必须包含出现在抽象类型其他约束中的所有生命周期参数。

use<..> 约束内,任何存在的生命周期参数必须出现在所有类型和 const 泛型参数之前,并且如果省略生命周期('_)被允许出现在 impl Trait 返回类型中,则可以存在。

由于必须按名称包含所有在作用域内的类型参数,因此 use<..> 约束不能在使用参数位置 impl Trait 的项的签名中使用,因为这些项在作用域内具有匿名类型参数。

Trait 定义中关联函数中存在的任何 use<..> 约束必须包含 trait 的所有泛型参数,包括 trait 的隐式 Self 泛型类型参数。

泛型和返回位置 impl Trait 之间的区别

在参数位置,impl Trait 在语义上与泛型类型参数非常相似。然而,在返回位置两者之间存在显著差异。使用 impl Trait,与泛型类型参数不同,函数选择返回类型,调用者无法选择返回类型。

函数:

#![allow(unused)]
fn main() {
trait Trait {}
fn foo<T: Trait>() -> T {
    // ...
panic!()
}
}

允许调用者确定返回类型 T,并返回该类型。

函数:

#![allow(unused)]
fn main() {
trait Trait {}
impl Trait for () {}
fn foo() -> impl Trait {
    // ...
}
}

不允许调用者确定返回类型。相反,函数选择返回类型,但只承诺它将实现 Trait

限制

impl Trait 只能作为非 extern 函数的参数或返回类型出现。它不能是 let 绑定的类型、字段类型,也不能出现在类型别名中。

类型参数

在具有类型参数声明的项的主体内,其类型参数的名称是类型:

#![allow(unused)]
fn main() {
fn to_vec<A: Clone>(xs: &[A]) -> Vec<A> {
    if xs.is_empty() {
        return vec![];
    }
    let first: A = xs[0].clone();
    let mut rest: Vec<A> = to_vec(&xs[1..]);
    rest.insert(0, first);
    rest
}
}

这里,first 的类型为 A,引用 to_vecA 类型参数;rest 的类型为 Vec<A>,元素类型为 A 的向量。

推断类型

Syntax
InferredType_

推断类型要求编译器根据可用的周围信息推断类型(如果可能)。

Example

推断类型通常用于泛型参数:

#![allow(unused)]
fn main() {
let x: Vec<_> = (0..10).collect();
}

推断类型不能在项签名中使用。

动态大小类型(Dynamically Sized Type)

大多数类型具有在编译时已知的固定大小,并实现 trait Sized。大小仅在运行时已知的类型称为_动态大小类型_(DST),或者非正式地称为未大小类型。切片trait 对象strDST 的示例。

此类类型只能在某些情况下使用:

  • 指向 DST指针类型 是有大小的,但其大小是指向有大小类型的指针的两倍
    • 指向切片和 str 的指针还存储元素数量。
    • 指向 trait 对象的指针还存储指向 vtable 的指针。
  • DST 可以作为具有特殊 ?Sized 约束的泛型类型参数的类型参数提供。当相应的关联类型声明具有 ?Sized 约束时,它们也可以用于关联类型定义。默认情况下,任何类型参数或关联类型都具有 Sized 约束,除非使用 ?Sized 放宽。
  • 可以为 DST 实现 trait。与泛型类型参数不同,在 trait 定义中 Self: ?Sized 是默认值。
  • 结构体可以包含 DST 作为最后一个字段;这使得结构体本身成为 DST

Note

变量、函数参数、const 项和 static 项必须是 Sized 的。

类型布局

类型的布局(Layout)是其大小(Size)、对齐(Alignment)和字段的相对偏移量。对于枚举,判别值(Discriminant)的布局和解释也是类型布局的一部分。

类型布局可以在每次编译时更改。我们只记录今天保证的内容,而不是试图记录具体做了什么。

请注意,即使具有相同布局的类型在跨函数边界传递时仍可能有所不同。有关类型的函数调用 ABI 兼容性,请参阅这里

大小和对齐

所有值都具有对齐和大小。

值的对齐指定存储该值的有效地址。对齐为 n 的值必须仅存储在 n 的倍数的地址处。例如,对齐为 2 的值必须存储在偶数地址处,而对齐为 1 的值可以存储在任何地址处。对齐以字节为单位,必须至少为 1,且始终为 2 的幂。值的对齐可以用 align_of_val 函数检查。

值的大小是具有该项类型的数组中连续元素之间的字节偏移量,包括对齐填充。值的大小始终是其对齐的倍数。请注意,某些类型是零大小的;0 被认为是任何对齐的倍数(例如,在某些平台上,类型 [u16; 0] 的大小为 0,对齐为 2)。值的大小可以用 size_of_val 函数检查。

所有值具有相同大小和对齐,并且两者在编译时已知的类型实现 Sized trait,可以用 size_ofalign_of 函数检查。不是 Sized 的类型称为动态大小类型。由于 Sized 类型的所有值共享相同的大小和对齐,我们将这些共享值分别称为类型的大小和类型的对齐。

原始数据布局

大多数原始类型的大小在此表中给出。

类型size_of::<Type>()
bool1
u8 / i81
u16 / i162
u32 / i324
u64 / i648
u128 / i12816
usize / isize见下文
f324
f648
char4

usizeisize 的大小足以包含目标平台上的每个地址。例如,在 32 位目标上,这是 4 字节,在 64 位目标上,这是 8 字节。

usizeisize 具有相同的大小和对齐。

原始类型的对齐是特定于平台的。在大多数情况下,它们的对齐等于它们的大小,但可能更小。特别是,i128u128 通常对齐到 4 或 8 字节,即使它们的大小是 16,并且在许多 32 位平台上,i64u64f64 仅对齐到 4 字节,而不是 8。

对于相同指定大小的固定宽度有符号和无符号整数变体,对齐保证相同 — 也就是说,对于给定大小 Nalign_of::<uN>() == align_of::<iN>()

指针和引用布局

指针和引用具有相同的布局。指针或引用的可变性不会改变布局。

指向有大小类型的指针具有与 usize 相同的大小和对齐。

指向未大小类型的指针是有大小的。指向未大小类型的指针的大小和对齐保证分别大于或等于指向有大小类型的指针的大小和对齐。

Note

虽然您不应该依赖于此,但目前所有指向 DST 的指针都是 usize 大小的两倍,并且具有相同的对齐。

数组布局

[T; N] 的数组大小为 size_of::<T>() * N,对齐与 T 相同。数组的布局使得数组的从零开始的第 n 个元素从数组开头偏移 n * size_of::<T>() 字节。

切片布局

切片与它们切片的数组部分具有相同的布局。

Note

这是关于原始 [T] 类型,而不是指向切片的指针(&[T]Box<[T]> 等)。

str 布局

字符串切片是字符的 UTF-8 表示,与 [u8] 类型的切片具有相同的布局。引用 &str 与引用 &[u8] 具有相同的布局。

元组布局

元组根据 Rust 表示布局。

此规则的例外是单元元组(()),它作为零大小类型保证大小为 0,对齐为 1。

Trait 对象布局

Trait 对象与 trait 对象所具有的值具有相同的布局。

Note

这是关于原始 trait 对象类型,而不是指向 trait 对象的指针(&dyn TraitBox<dyn Trait> 等)。

闭包布局

闭包没有布局保证。

表示

所有用户定义的复合类型(structenumunion)都具有指定类型布局的表示

类型的可能表示是:

可以通过将 repr 属性应用于类型来更改类型的表示。以下示例显示了具有 C 表示的结构体。

#![allow(unused)]
fn main() {
#[repr(C)]
struct ThreeInts {
    first: i16,
    second: i8,
    third: i32
}
}

对齐可以分别使用 alignpacked 修饰符提高或降低。它们会改变属性中指定的表示。如果未指定表示,则改变默认表示。

#![allow(unused)]
fn main() {
// Default representation, alignment lowered to 2.
#[repr(packed(2))]
struct PackedStruct {
    first: i16,
    second: i8,
    third: i32
}

// C representation, alignment raised to 8
#[repr(C, align(8))]
struct AlignedStruct {
    first: i16,
    second: i8,
    third: i32
}
}

Note

由于表示是项上的属性,因此表示不依赖于泛型参数。任何两个具有相同名称的类型具有相同的表示。例如,Foo<Bar>Foo<Baz> 都具有相同的表示。

类型的表示可以更改字段之间的填充,但不会更改字段本身的布局。例如,包含具有 Rust 表示的结构体 Inner 的具有 C 表示的结构体不会更改 Inner 的布局。

Rust 表示

Rust 表示是没有 repr 属性的具名类型的默认表示。通过 repr 属性显式使用此表示保证与完全省略属性相同。

此表示做出的唯一数据布局保证是健全性所需的那些。这些是:

  1. 字段的偏移量可被该字段的对齐整除。
  2. 类型的对齐至少是其字段的最大对齐。

对于结构体,进一步保证字段不重叠。也就是说,字段可以排序,使得任何字段的偏移量加上大小小于或等于排序中下一个字段的偏移量。排序不必与类型声明中指定的字段顺序相同。

请注意,此保证并不意味着字段具有不同的地址:零大小类型可能与同一结构体中的其他字段具有相同的地址。

此表示不提供其他数据布局保证。

C 表示

C 表示设计用于双重目的。一个目的是创建与 C 语言互操作的类型。第二个目的是创建可以安全地执行依赖于数据布局的操作(例如将值重新解释为不同类型)的类型。

由于这种双重目的,可以创建对与 C 编程语言接口无用的类型。

此表示可以应用于结构体、联合体和枚举。例外是零变体枚举,对于它们 C 表示是错误的。

#[repr(C)] 结构体

结构体的对齐是其中对齐最大的字段的对齐,如果没有字段则为 1。

字段的大小和偏移量由以下算法确定。

从当前偏移量 0 字节开始。

对于结构体中按声明顺序的每个字段,首先确定字段的大小和对齐。如果当前偏移量不是字段对齐的倍数,则向当前偏移量添加填充字节,直到它是字段对齐的倍数。字段的偏移量是当前偏移量的值。然后将当前偏移量增加字段的大小。

最后,结构体的大小是当前偏移量向上舍入到结构体对齐的最近倍数。

以下是用伪代码描述的此算法。

/// Returns the amount of padding needed after `offset` to ensure that the
/// following address will be aligned to `alignment`.
fn padding_needed_for(offset: usize, alignment: usize) -> usize {
    let misalignment = offset % alignment;
    if misalignment > 0 {
        // round up to next multiple of `alignment`
        alignment - misalignment
    } else {
        // already a multiple of `alignment`
        0
    }
}

struct.alignment = struct.fields().map(|field| field.alignment).max();

let current_offset = 0;

for field in struct.fields_in_declaration_order() {
    // Increase the current offset so that it's a multiple of the alignment
    // of this field. For the first field, this will always be zero.
    // The skipped bytes are called padding bytes.
    current_offset += padding_needed_for(current_offset, field.alignment);

    struct[field].offset = current_offset;

    current_offset += field.size;
}

struct.size = current_offset + padding_needed_for(current_offset, struct.alignment);

Warning

此伪代码使用朴素算法,为了清晰起见忽略了溢出问题。要在实际代码中执行内存布局计算,请使用 Layout

Note

此算法可以产生零大小的结构体。在 C 中,像 struct Foo { } 这样的空结构体声明是非法的。但是,gcc 和 clang 都支持启用此类结构体的选项,并将它们的大小分配为零。相比之下,C++ 给空结构体大小为 1,除非它们被继承或它们是具有 [[no_unique_address]] 属性的字段,在这种情况下它们不会增加结构体的总大小。

#[repr(C)] 联合体

使用 #[repr(C)] 声明的联合体将具有与目标平台 C 语言中等效 C 联合体声明相同的大小和对齐。

联合体的大小为其所有字段的最大大小舍入到其对齐,对齐为其所有字段的最大对齐。这些最大值可能来自不同的字段。每个字段位于联合体开头的字节偏移量 0 处。

#![allow(unused)]
fn main() {
#[repr(C)]
union Union {
    f1: u16,
    f2: [u8; 4],
}

assert_eq!(std::mem::size_of::<Union>(), 4);  // From f2
assert_eq!(std::mem::align_of::<Union>(), 2); // From f1

assert_eq!(std::mem::offset_of!(Union, f1), 0);
assert_eq!(std::mem::offset_of!(Union, f2), 0);

#[repr(C)]
union SizeRoundedUp {
   a: u32,
   b: [u16; 3],
}

assert_eq!(std::mem::size_of::<SizeRoundedUp>(), 8);  // Size of 6 from b,
                                                      // rounded up to 8 from
                                                      // alignment of a.
assert_eq!(std::mem::align_of::<SizeRoundedUp>(), 4); // From a

assert_eq!(std::mem::offset_of!(SizeRoundedUp, a), 0);
assert_eq!(std::mem::offset_of!(SizeRoundedUp, b), 0);
}

#[repr(C)] 无字段枚举

对于无字段枚举C 表示具有目标平台 C ABI 的默认 enum 大小和对齐。

Note

C 中的枚举表示是实现定义的,所以这实际上是一个“最佳猜测“。特别是,当感兴趣的 C 代码使用某些标志编译时,这可能是不正确的。

Warning

C 语言中的 enum 和 Rust 的具有此表示的无字段枚举之间存在关键差异。C 中的 enum 主要是 typedef 加上一些命名常量;换句话说,enum 类型的对象可以持有任何整数值。例如,这在 C 中经常用于位标志。相比之下,Rust 的无字段枚举只能合法地持有判别值,其他一切都是未定义行为。因此,在 FFI 中使用无字段枚举来建模 C enum 通常是错误的。

#[repr(C)] 带字段的枚举

带字段的 repr(C) 枚举的表示是一个具有两个字段的 repr(C) 结构体,在 C 中也称为“标记联合体“:

  • 枚举的 repr(C) 版本,所有字段已移除(“标记”)
  • 每个具有字段的变体的字段的 repr(C) 结构体的 repr(C) 联合体(“有效载荷”)

Note

由于 repr(C) 结构体和联合体的表示,如果变体具有单个字段,将该字段直接放在联合体中或将其包装在结构体中没有区别;因此,希望操作此类 enum 表示的任何系统可以使用对他们来说更方便或更一致的任何形式。

#![allow(unused)]
fn main() {
// This Enum has the same representation as ...
#[repr(C)]
enum MyEnum {
    A(u32),
    B(f32, u64),
    C { x: u32, y: u8 },
    D,
 }

// ... this struct.
#[repr(C)]
struct MyEnumRepr {
    tag: MyEnumDiscriminant,
    payload: MyEnumFields,
}

// This is the discriminant enum.
#[repr(C)]
enum MyEnumDiscriminant { A, B, C, D }

// This is the variant union.
#[repr(C)]
union MyEnumFields {
    A: MyAFields,
    B: MyBFields,
    C: MyCFields,
    D: MyDFields,
}

#[repr(C)]
#[derive(Copy, Clone)]
struct MyAFields(u32);

#[repr(C)]
#[derive(Copy, Clone)]
struct MyBFields(f32, u64);

#[repr(C)]
#[derive(Copy, Clone)]
struct MyCFields { x: u32, y: u8 }

// This struct could be omitted (it is a zero-sized type), and it must be in
// C/C++ headers.
#[repr(C)]
#[derive(Copy, Clone)]
struct MyDFields;
}

原始表示

原始表示是与原始整数类型同名的表示。即:u8u16u32u64u128usizei8i16i32i64i128isize

原始表示只能应用于枚举,并且根据枚举是否有字段具有不同的行为。零变体枚举具有原始表示是错误的。将两个原始表示组合在一起是错误的。

无字段枚举的原始表示

对于无字段枚举,原始表示将大小和对齐设置为与同名原始类型相同。例如,具有 u8 表示的无字段枚举只能在 0 到 255(含)之间具有判别值。

带字段枚举的原始表示

原始表示枚举的表示是每个具有字段的变体的 repr(C) 结构体的 repr(C) 联合体。联合体中每个结构体的第一个字段是枚举的原始表示版本,所有字段已移除(“标记”),其余字段是该变体的字段。

Note

如果标记在联合体中拥有自己的成员,此表示是不变的,如果这使操作对您更清晰的话(尽管要遵循 C++ 标准,标记成员应包装在 struct 中)。

#![allow(unused)]
fn main() {
// This enum has the same representation as ...
#[repr(u8)]
enum MyEnum {
    A(u32),
    B(f32, u64),
    C { x: u32, y: u8 },
    D,
 }

// ... this union.
#[repr(C)]
union MyEnumRepr {
    A: MyVariantA,
    B: MyVariantB,
    C: MyVariantC,
    D: MyVariantD,
}

// This is the discriminant enum.
#[repr(u8)]
#[derive(Copy, Clone)]
enum MyEnumDiscriminant { A, B, C, D }

#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantA(MyEnumDiscriminant, u32);

#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantB(MyEnumDiscriminant, f32, u64);

#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantC { tag: MyEnumDiscriminant, x: u32, y: u8 }

#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantD(MyEnumDiscriminant);
}

将带字段枚举的原始表示与 #[repr(C)] 结合

对于带字段的枚举,也可以将 repr(C) 和原始表示组合(例如 repr(C, u8))。这通过将判别枚举的表示更改为所选原始类型来修改 repr(C)。因此,如果您选择 u8 表示,则判别枚举将具有 1 字节的大小和对齐。

前面示例中的判别枚举变为:

#![allow(unused)]
fn main() {
#[repr(C, u8)] // `u8` was added
enum MyEnum {
    A(u32),
    B(f32, u64),
    C { x: u32, y: u8 },
    D,
 }

// ...

#[repr(u8)] // So `u8` is used here instead of `C`
enum MyEnumDiscriminant { A, B, C, D }

// ...
}

例如,使用 repr(C, u8) 枚举不可能有 257 个唯一判别值(“标记”),而仅具有 repr(C) 属性的相同枚举将没有任何问题地编译。

除了 repr(C) 之外使用原始表示可以更改枚举从 repr(C) 形式的大小:

#![allow(unused)]
fn main() {
#[repr(C)]
enum EnumC {
    Variant0(u8),
    Variant1,
}

#[repr(C, u8)]
enum Enum8 {
    Variant0(u8),
    Variant1,
}

#[repr(C, u16)]
enum Enum16 {
    Variant0(u8),
    Variant1,
}

// The size of the C representation is platform dependent
assert_eq!(std::mem::size_of::<EnumC>(), 8);
// One byte for the discriminant and one byte for the value in Enum8::Variant0
assert_eq!(std::mem::size_of::<Enum8>(), 2);
// Two bytes for the discriminant and one byte for the value in Enum16::Variant0
// plus one byte of padding.
assert_eq!(std::mem::size_of::<Enum16>(), 4);
}

对齐修饰符

alignpacked 修饰符可用于分别提高或降低 structunion 的对齐。packed 也可以更改字段之间的填充(尽管它不会更改任何字段内部的填充)。单独使用时,alignpacked 不提供关于结构体布局中字段顺序或枚举变体布局的保证,尽管它们可以与提供此类保证的表示(如 C)组合使用。

对齐以整数参数形式指定,形式为 #[repr(align(x))]#[repr(packed(x))]。对齐值必须是从 1 到 229 的 2 的幂。对于 packed,如果未给出值(如 #[repr(packed)]),则值为 1。

对于 align,如果指定的对齐小于没有 align 修饰符的类型的对齐,则对齐不受影响。

对于 packed,如果指定的对齐大于没有 packed 修饰符的类型的对齐,则对齐和布局不受影响。

出于定位字段的目的,每个字段的对齐是指定对齐和字段类型对齐中的较小者。

字段间填充保证是满足每个字段(可能更改的)对齐所需的最小值(尽管请注意,单独使用时,packed 不提供关于字段顺序的任何保证)。这些规则的一个重要后果是,具有 #[repr(packed(1))](或 #[repr(packed)])的类型将没有字段间填充。

alignpacked 修饰符不能应用于同一类型,并且 packed 类型不能传递地包含另一个 aligned 类型。alignpacked 只能应用于 RustC 表示。

align 修饰符也可以应用于 enum。当应用时,对 enum 对齐的效果与将 enum 包装在具有相同 align 修饰符的新类型 struct 中相同。

Note

不允许引用未对齐的字段,因为这是未定义行为。当字段由于对齐修饰符而未对齐时,请考虑以下使用引用和解引用的选项:

#![allow(unused)]
fn main() {
#[repr(packed)]
struct Packed {
    f1: u8,
    f2: u16,
}
let mut e = Packed { f1: 1, f2: 2 };
// Instead of creating a reference to a field, copy the value to a local variable.
let x = e.f2;
// Or in situations like `println!` which creates a reference, use braces
// to change it to a copy of the value.
println!("{}", {e.f2});
// Or if you need a pointer, use the unaligned methods for reading and writing
// instead of dereferencing the pointer directly.
let ptr: *const u16 = &raw const e.f2;
let value = unsafe { ptr.read_unaligned() };
let mut_ptr: *mut u16 = &raw mut e.f2;
unsafe { mut_ptr.write_unaligned(3) }
}

transparent 表示

transparent 表示只能用于具有单个变体的 structenum,该变体具有:

  • 任意数量的大小为 0 且对齐为 1 的字段(例如 PhantomData<T>),以及
  • 最多一个其他字段。

具有此表示的结构体和枚举与唯一的非大小 0 非对齐 1 字段(如果存在)或单元(否则)具有相同的布局和 ABI。

这与 C 表示不同,因为具有 C 表示的结构体将始终具有 C struct 的 ABI,而例如,具有原始字段的 transparent 表示的结构体将具有原始字段的 ABI。

由于此表示将类型布局委托给另一个类型,因此不能与任何其他表示一起使用。

内部可变性

有时类型需要在具有多个别名的情况下被修改。在 Rust 中,这是通过称为_内部可变性_(Interior Mutability)的模式实现的。

如果类型的内部状态可以通过共享引用更改,则该类型具有内部可变性。

这与通常的要求(共享引用指向的值不被修改)相矛盾。

std::cell::UnsafeCell<T> 类型是禁用此要求的唯一允许方式。当 UnsafeCell<T> 被不可变别名时,修改或获取其包含的 T 的可变引用仍然是安全的。

与所有其他类型一样,具有多个 &mut UnsafeCell<T> 别名是未定义行为。

可以通过使用 UnsafeCell<T> 作为字段来创建具有内部可变性的其他类型。标准库提供了各种提供安全内部可变性 API 的类型。

例如,std::cell::RefCell<T> 使用运行时借用检查来确保围绕多个引用的通常规则。

std::sync::atomic 模块包含包装仅通过原子操作访问的值的类型,允许该值在线程之间共享和修改。

子类型(Subtyping)和型变(Variance)

子类型是隐式的,可以在类型检查或推断的任何阶段发生。

子类型仅限于两种情况:关于生命周期的型变和具有高阶生命周期的类型之间的型变。如果我们要从类型中擦除生命周期,那么唯一的子类型将是由类型相等引起的。

考虑以下示例:字符串字面量始终具有 'static 生命周期。尽管如此,我们可以将 s 赋值给 t

#![allow(unused)]
fn main() {
fn bar<'a>() {
    let s: &'static str = "hi";
    let t: &'a str = s;
}
}

由于 'static 比生命周期参数 'a 活得更久,&'static str&'a str 的子类型。

高阶 函数指针trait 对象 具有另一种子类型关系。它们是通过替换高阶生命周期给出的类型的子类型。一些示例:

#![allow(unused)]
fn main() {
// Here 'a is substituted for 'static
let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_);
let supertype: &(fn(&'static i32) -> &'static i32) = subtype;

// This works similarly for trait objects
let subtype: &(dyn for<'a> Fn(&'a i32) -> &'a i32) = &|x| x;
let supertype: &(dyn Fn(&'static i32) -> &'static i32) = subtype;

// We can also substitute one higher-ranked lifetime for another
let subtype: &(for<'a, 'b> fn(&'a i32, &'b i32)) = &((|x, y| {}) as fn(&_, &_));
let supertype: &for<'c> fn(&'c i32, &'c i32) = subtype;
}

型变

型变是泛型类型相对于其参数的属性。泛型类型在参数中的型变是参数的子类型如何影响类型的子类型。

  • 如果 TU 的子类型意味着 F<T>F<U> 的子类型(子类型“穿透“),则 F<T>T 上是协变的
  • 如果 TU 的子类型意味着 F<U>F<T> 的子类型,则 F<T>T 上是逆变的
  • 否则 F<T>T 上是不变的(无法推导出子类型关系)

类型的型变自动确定如下

类型'a 中的型变T 中的型变
&'a T协变协变
&'a mut T协变不变
*const T协变
*mut T不变
[T][T; n]协变
fn() -> T协变
fn(T) -> ()逆变
std::cell::UnsafeCell<T>不变
std::marker::PhantomData<T>协变
dyn Trait<T> + 'a协变不变

其他 structenumunion 类型的型变通过查看其字段类型的型变来决定。如果参数在具有不同型变的位置使用,则该参数是不变的。例如,以下结构体在 'aT 上是协变的,在 'b'cU 上是不变的。

#![allow(unused)]
fn main() {
use std::cell::UnsafeCell;
struct Variance<'a, 'b, 'c, T, U: 'a> {
    x: &'a U,               // This makes `Variance` covariant in 'a, and would
                            // make it covariant in U, but U is used later
    y: *const T,            // Covariant in T
    z: UnsafeCell<&'b f64>, // Invariant in 'b
    w: *mut U,              // Invariant in U, makes the whole struct invariant

    f: fn(&'c ()) -> &'c () // Both co- and contravariant, makes 'c invariant
                            // in the struct.
}
}

当在 structenumunion 之外使用时,参数的型变在每个位置单独检查。

#![allow(unused)]
fn main() {
use std::cell::UnsafeCell;
fn generic_tuple<'short, 'long: 'short>(
    // 'long is used inside of a tuple in both a co- and invariant position.
    x: (&'long u32, UnsafeCell<&'long u32>),
) {
    // As the variance at these positions is computed separately,
    // we can freely shrink 'long in the covariant position.
    let _: (&'short u32, UnsafeCell<&'long u32>) = x;
}

fn takes_fn_ptr<'short, 'middle: 'short>(
    // 'middle is used in both a co- and contravariant position.
    f: fn(&'middle ()) -> &'middle (),
) {
    // As the variance at these positions is computed separately,
    // we can freely shrink 'middle in the covariant position
    // and extend it in the contravariant position.
    let _: fn(&'static ()) -> &'short () = f;
}
}

Trait 和生命周期约束(Bounds)

Syntax
BoundsBound ( + Bound )* +?

BoundLifetime | TraitBound | UseBound

TraitBound
      ( ? | ForLifetimes )? TypePath
    | ( ( ? | ForLifetimes )? TypePath )

LifetimeBounds → ( Lifetime + )* Lifetime?

Lifetime
      LIFETIME_OR_LABEL
    | 'static
    | '_

UseBounduse UseBoundGenericArgs

UseBoundGenericArgs
      < >
    | < ( UseBoundGenericArg , )* UseBoundGenericArg ,? >

UseBoundGenericArg
      Lifetime
    | IDENTIFIER
    | Self

Trait 和生命周期约束为泛型项提供了一种方式来限制哪些类型和生命周期用作其参数。约束可以在 where 子句中的任何类型上提供。对于某些常见情况,还有更短的形式:

  • 在声明泛型参数之后编写的约束:fn f<A: Copy>() {}fn f<A>() where A: Copy {} 相同。
  • 在 trait 声明中作为超级 traittrait Circle : Shape {} 等同于 trait Circle where Self : Shape {}
  • 在 trait 声明中作为关联类型的约束:trait A { type B: Copy; } 等同于 trait A where Self::B: Copy { type B; }

使用项时必须满足项上的约束。在对泛型项进行类型检查和借用检查时,约束可用于确定 trait 已为类型实现。例如,给定 Ty: Trait

  • 在泛型函数的主体中,可以在 Ty 值上调用 Trait 的方法。同样可以使用 Trait 上的关联常量。
  • 可以使用 Trait 的关联类型。
  • 具有 T: Trait 约束的泛型函数和类型可以使用 Ty 作为 T
#![allow(unused)]
fn main() {
type Surface = i32;
trait Shape {
    fn draw(&self, surface: Surface);
    fn name() -> &'static str;
}

fn draw_twice<T: Shape>(surface: Surface, sh: T) {
    sh.draw(surface);           // Can call method because T: Shape
    sh.draw(surface);
}

fn copy_and_draw_twice<T: Copy>(surface: Surface, sh: T) where T: Shape {
    let shape_copy = sh;        // doesn't move sh because T: Copy
    draw_twice(surface, sh);    // Can use generic function because T: Shape
}

struct Figure<S: Shape>(S, S);

fn name_figure<U: Shape>(
    figure: Figure<U>,          // Type Figure<U> is well-formed because U: Shape
) {
    println!(
        "Figure of two {}",
        U::name(),              // Can use associated function
    );
}
}

不使用项的参数或高阶生命周期的约束在定义项时检查。此类约束为 false 是错误的。

CopyCloneSized 约束在使用项时也会针对某些泛型类型进行检查,即使使用不提供具体类型。在可变引用、trait 对象切片上具有 CopyClone 作为约束是错误的。在 trait 对象或切片上具有 Sized 作为约束是错误的。

#![allow(unused)]
fn main() {
struct A<'a, T>
where
    i32: Default,           // Allowed, but not useful
    i32: Iterator,          // Error: `i32` is not an iterator
    &'a mut T: Copy,        // (at use) Error: the trait bound is not satisfied
    [T]: Sized,             // (at use) Error: size cannot be known at compilation
{
    f: &'a T,
}
struct UsesA<'a, T>(A<'a, T>);
}

Trait 和生命周期约束也用于命名 trait 对象

?Sized

? 仅用于放宽类型参数关联类型的隐式 Sized trait 约束。?Sized 不能用作其他类型的约束。

生命周期约束

生命周期约束可以应用于类型或其他生命周期。

约束 'a: 'b 通常读作 'a 'b 活得更久'a: 'b 意味着 'a 至少与 'b 持续一样长,因此引用 &'a ()&'b () 有效时有效。

#![allow(unused)]
fn main() {
fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b {
    y = x;                      // &'a i32 is a subtype of &'b i32 because 'a: 'b
    let r: &'b &'a i32 = &&0;   // &'b &'a i32 is well formed because 'a: 'b
}
}

T: 'a 意味着 T 的所有生命周期参数比 'a 活得更久。例如,如果 'a 是无约束的生命周期参数,则 i32: 'static&'static str: 'a 被满足,但 Vec<&'a ()>: 'static 不被满足。

高阶 trait 约束

Syntax
ForLifetimesfor GenericParams

Trait 约束可以是高阶的关于生命周期。这些约束指定对所有生命周期都为 true 的约束。例如,约束如 for<'a> &'a T: PartialEq<i32> 将要求如下实现

#![allow(unused)]
fn main() {
struct T;
impl<'a> PartialEq<i32> for &'a T {
    // ...
   fn eq(&self, other: &i32) -> bool {true}
}
}

然后可以用于将任何生命周期的 &'a Ti32 进行比较。

这里只能使用高阶约束,因为引用的生命周期比函数上任何可能的生命周期参数都短:

#![allow(unused)]
fn main() {
fn call_on_ref_zero<F>(f: F) where for<'a> F: Fn(&'a i32) {
    let zero = 0;
    f(&zero);
}
}

高阶生命周期也可以在 trait 之前指定:唯一的区别是生命周期参数的[作用域]scope,它仅延伸到以下 trait 的末尾,而不是整个约束。此函数等同于上一个函数。

#![allow(unused)]
fn main() {
fn call_on_ref_zero<F>(f: F) where F: for<'a> Fn(&'a i32) {
    let zero = 0;
    f(&zero);
}
}

隐式约束

类型为 well-formed 所需的生命周期约束有时会被推断。

#![allow(unused)]
fn main() {
fn requires_t_outlives_a<'a, T>(x: &'a T) {}
}

类型参数 T 被要求比 'a 活得更久,以便类型 &'a T 为 well-formed。这是被推断的,因为函数签名包含类型 &'a T,该类型仅在 T: 'a 成立时有效。

隐式约束被添加到函数的所有参数和输出中。在 requires_t_outlives_a 内部,您可以假设 T: 'a 成立,即使您没有显式指定:

#![allow(unused)]
fn main() {
fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}

fn requires_t_outlives_a<'a, T>(x: &'a T) {
    // This compiles, because `T: 'a` is implied by
    // the reference type `&'a T`.
    requires_t_outlives_a_not_implied::<'a, T>();
}
}
#![allow(unused)]
fn main() {
fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}
fn not_implied<'a, T>() {
    // This errors, because `T: 'a` is not implied by
    // the function signature.
    requires_t_outlives_a_not_implied::<'a, T>();
}
}

只有生命周期约束被隐式推断,trait 约束仍然必须显式添加。因此以下示例会导致错误:

#![allow(unused)]
fn main() {
use std::fmt::Debug;
struct IsDebug<T: Debug>(T);
// error[E0277]: `T` doesn't implement `Debug`
fn doesnt_specify_t_debug<T>(x: IsDebug<T>) {}
}

生命周期约束也会为类型定义和 impl 块推断:

#![allow(unused)]
fn main() {
struct Struct<'a, T> {
    // This requires `T: 'a` to be well-formed
    // which is inferred by the compiler.
    field: &'a T,
}

enum Enum<'a, T> {
    // This requires `T: 'a` to be well-formed,
    // which is inferred by the compiler.
    //
    // Note that `T: 'a` is required even when only
    // using `Enum::OtherVariant`.
    SomeVariant(&'a T),
    OtherVariant,
}

trait Trait<'a, T: 'a> {}

// This would error because `T: 'a` is not implied by any type
// in the impl header.
//     impl<'a, T> Trait<'a, T> for () {}

// This compiles as `T: 'a` is implied by the self type `&'a T`.
impl<'a, T> Trait<'a, T> for &'a T {}
}

Use 约束

某些约束列表可以包含 use<..> 约束来控制 impl Trait 抽象返回类型捕获哪些泛型参数。有关更多详细信息,请参阅精确捕获

类型强制转换

类型强制转换(Type Coercion)是更改值类型的隐式操作。它们在特定位置自动发生,并且对实际强制转换的类型有高度限制。

强制转换允许的任何转换也可以通过类型转换运算符 as 显式执行。

强制转换最初在 RFC 401 中定义,并在 RFC 1558 中扩展。

强制转换站点

强制转换只能发生在程序中的某些强制转换站点;这些通常是所需类型是显式的或可以通过从显式类型传播推导的位置(没有类型推断)。可能的强制转换站点是:

  • 给定显式类型的 let 语句。

    例如,在以下情况下,&mut 42 被强制转换为具有类型 &i8

    #![allow(unused)]
    fn main() {
    let _: &i8 = &mut 42;
    }
  • staticconst 项声明(类似于 let 语句)。
  • 函数调用的参数

    被强制转换的值是实际参数,它被强制转换为形式参数的类型。

    例如,在以下情况下,&mut 42 被强制转换为具有类型 &i8

    fn bar(_: &i8) { }
    
    fn main() {
        bar(&mut 42);
    }

    对于方法调用,接收者(self 参数)类型的强制转换方式不同,请参阅方法调用表达式文档了解详情。

  • 结构体、联合体或枚举变体字段的实例化

    例如,在以下情况下,&mut 42 被强制转换为具有类型 &i8

    struct Foo<'a> { x: &'a i8 }
    
    fn main() {
        Foo { x: &mut 42 };
    }
  • 函数结果 — 如果块的最后一行不是以分号终止的,或者是 return 语句中的任何表达式

    例如,在以下情况下,x 被强制转换为具有类型 &dyn Display

    #![allow(unused)]
    fn main() {
    use std::fmt::Display;
    fn foo(x: &u32) -> &dyn Display {
        x
    }
    }
  • 赋值表达式中的赋值值操作数

    例如,在以下情况下,y 被强制转换为具有类型 &i8

    #![allow(unused)]
    fn main() {
    let mut x = &0i8;
    let y = &mut 42i8;
    x = y;
    }

如果这些强制转换站点之一中的表达式是强制转换传播表达式,则该表达式中的相关子表达式也是强制转换站点。传播从这些新的强制转换站点递归。传播表达式及其相关子表达式是:

  • 数组字面量,其中数组类型为 [U; n]。数组字面量中的每个子表达式都是强制转换为类型 U 的强制转换站点。
  • 带重复语法的数组字面量,其中数组类型为 [U; n]。重复的子表达式是强制转换为类型 U 的强制转换站点。
  • 元组,其中元组是类型为 (U_0, U_1, ..., U_n) 的强制转换站点。每个子表达式是强制转换为相应类型的强制转换站点,例如第零个子表达式是强制转换为类型 U_0 的强制转换站点。
  • 括号子表达式((e)):如果表达式类型为 U,则子表达式是强制转换为 U 的强制转换站点。
  • 块:如果块类型为 U,则块中的最后一个表达式(如果不是以分号终止的)是强制转换为 U 的强制转换站点。这包括作为控制流语句一部分的块,例如 if/else,如果块具有已知类型。

强制转换类型

允许在以下类型之间进行强制转换:

  • TU,如果 TU子类型自反情况
  • T_1T_3,其中 T_1 强制转换为 T_2T_2 强制转换为 T_3传递情况

    请注意,这尚未完全支持。

  • &mut T&T
  • *mut T*const T
  • &T*const T
  • &mut T*mut T
  • &T&mut T&U,如果 T 实现 Deref<Target = U>。例如:

    use std::ops::Deref;
    
    struct CharContainer {
        value: char,
    }
    
    impl Deref for CharContainer {
        type Target = char;
    
        fn deref<'a>(&'a self) -> &'a char {
            &self.value
        }
    }
    
    fn foo(arg: &char) {}
    
    fn main() {
        let x = &mut CharContainer { value: 'y' };
        foo(x); //&mut CharContainer is coerced to &char.
    }
  • &mut T&mut U,如果 T 实现 DerefMut<Target = U>
  • TyCtor(T) 到 TyCtor(U),其中 TyCtor(T) 是以下之一

    • &T
    • &mut T
    • *const T
    • *mut T
    • Box<T>

    并且 U 可以通过[未大小强制转换][unsized coercion](#unsized-coercions)从 T 获得。

  • 函数项类型到 fn 指针
  • 非捕获闭包到 fn 指针
  • ! 到任何 T

未大小强制转换

以下强制转换称为未大小强制转换,因为它们涉及将类型转换为未大小类型,并且在少数情况下允许其他强制转换不允许的情况,如上所述。它们仍然可以在强制转换可以发生的任何其他地方发生。

两个 trait UnsizeCoerceUnsized 用于协助此过程并将其暴露给库使用。以下是内置的,如果 T 可以通过其中之一强制转换为 U,则将为 T 提供 Unsize<U> 的实现:

  • [T; n][T]
  • Tdyn U,当 T 实现 U + Sized,并且 Udyn 兼容的
  • dyn Tdyn U,当 UT超级 trait之一时。
    • 这允许丢弃自动 trait,即 dyn T + Autodyn U 是允许的。
    • 如果主 trait 具有自动 trait 作为超级 trait,则允许添加自动 trait,即给定 trait T: U + Send {}dyn Tdyn T + Send 或到 dyn U + Send 强制转换是允许的。
  • Foo<..., T, ...>Foo<..., U, ...>,当:
    • Foo 是结构体。
    • T 实现 Unsize<U>
    • Foo 的最后一个字段具有涉及 T 的类型。
    • 如果该字段类型为 Bar<T>,则 Bar<T> 实现 Unsize<Bar<U>>
    • T 不是任何其他字段类型的一部分。

此外,类型 Foo<T> 可以在 T 实现 Unsize<U>CoerceUnsized<Foo<U>> 时实现 CoerceUnsized<Foo<U>>。这允许它提供到 Foo<U> 的未大小强制转换。

Note

虽然未大小强制转换的定义和实现已经稳定,但这些 trait 本身尚未稳定,因此不能在稳定版 Rust 中直接使用。

最小上界强制转换

在某些上下文中,编译器必须将多个类型强制转换在一起以尝试找到最通用的类型。这称为“最小上界“强制转换。LUB 强制转换仅在以下情况下使用:

  • 为一系列 if 分支找到公共类型。
  • 为一系列 match 分支找到公共类型。
  • 为数组元素找到公共类型。
  • 在 break 操作数和最终块操作数之间为标记块表达式找到公共类型。
  • 在 break 操作数之间为带 break 表达式的 loop 表达式找到公共类型。
  • 为具有多个 return 语句的闭包的返回类型找到类型。
  • 检查具有多个 return 语句的函数的返回类型。

在每种情况下,都有一组类型 T0..Tn 要相互强制转换为某个目标类型 T_t,该类型最初是未知的。

计算 LUB 强制转换是迭代完成的。目标类型 T_t 从类型 T0 开始。对于每个新类型 Ti,我们考虑是否

  • 如果 Ti 可以强制转换为当前目标类型 T_t,则不做任何更改。
  • 否则,检查 T_t 是否可以强制转换为 Ti;如果是,则将 T_t 更改为 Ti。(此检查还取决于到目前为止考虑的所有源表达式是否具有隐式强制转换。)
  • 如果没有,尝试计算 T_tTi 的相互超类型,这将成为新的目标类型。

示例:

#![allow(unused)]
fn main() {
let (a, b, c) = (0, 1, 2);
// For if branches
let bar = if true {
    a
} else if false {
    b
} else {
    c
};

// For match arms
let baw = match 42 {
    0 => a,
    1 => b,
    _ => c,
};

// For array elements
let bax = [a, b, c];

// For closure with multiple return statements
let clo = || {
    if true {
        a
    } else if false {
        b
    } else {
        c
    }
};
let baz = clo();

// For type checking of function with multiple return statements
fn foo() -> i32 {
    let (a, b, c) = (0, 1, 2);
    match 42 {
        0 => a,
        1 => b,
        _ => c,
    }
}
}

在这些示例中,ba* 的类型通过 LUB 强制转换找到。编译器在处理函数 foo 时检查 abc 的 LUB 强制转换结果是否为 i32

注意

此描述显然是非正式的。使其更精确预计将作为更精确地指定 Rust 类型检查器的总体努力的一部分进行。

发散

发散表达式是永不完成正常执行的表达式。

#![allow(unused)]
fn main() {
fn diverges() -> ! {
    panic!("This function never returns!");
}

fn example() {
    let x: i32 = diverges(); // This line never completes.
    println!("This is never printed: {x}");
}
}

有关特定表达式发散行为,请参阅以下规则:

Note

panic! 宏和相关的生成 panic 的宏(如 unreachable!)也具有类型 ! 并且是发散的。

任何类型为 ! 的表达式都是发散表达式。然而,发散表达式不限于类型 !;其他类型的表达式也可能发散(例如,Some(loop {})的类型为Option<!>`)。

Note

虽然 ! 被认为是无人居住的类型,但类型无人居住不足以使其发散。

#![allow(unused)]
fn main() {
enum Empty {}
fn make_never() -> ! {loop{}}
fn make_empty() -> Empty {loop{}}

fn diverging() -> ! {
    // This has a type of `!`.
    // So, the entire function is considered diverging.
    make_never();
    // OK: The type of the body is `!` which matches the return type.
}
fn not_diverging() -> ! {
    // This type is uninhabited.
    // However, the entire function is not considered diverging.
    make_empty();
    // ERROR: The type of the body is `()` but expected type `!`.
}
}

Note

发散可以传播到周围的块。参见 expr.block.diverging

回退

如果要推断的类型仅与发散表达式统一,则该类型将被推断为 !

Example

#![allow(unused)]
fn main() {
fn foo() -> i32 { 22 }
match foo() {
    // ERROR: The trait bound `!: Default` is not satisfied.
    4 => Default::default(),
    _ => return,
};
}

2024 Edition differences

在 2024 版本之前,该类型被推断为 ()

Note

重要的是,类型统一可能是结构化发生的,因此回退 ! 可能是更大类型的一部分。以下代码可以编译:

#![allow(unused)]
fn main() {
fn foo() -> i32 { 22 }
// This has the type `Option<!>`, not `!`
match foo() {
    4 => Default::default(),
    _ => Some(return),
};
}

析构器

已初始化变量临时值超出作用域时,其析构器(Destructor)将运行或被丢弃(Drop)。赋值也会运行其左操作数的析构器(如果已初始化)。如果变量已部分初始化,则只丢弃其已初始化的字段。

类型 T 的析构器包括:

  1. 如果 T: Drop,调用 <T as core::ops::Drop>::drop
  2. 递归运行其所有字段的析构器。
    • 结构体的字段按声明顺序丢弃。
    • 活动枚举变体的字段按声明顺序丢弃。
    • 元组的字段按顺序丢弃。
    • 数组或拥有切片的元素从第一个元素到最后一个元素丢弃。
    • 闭包通过移动捕获的变量按未指定顺序丢弃。
    • Trait 对象运行底层类型的析构器。
    • 其他类型不会导致任何进一步的丢弃。

如果必须手动运行析构器,例如实现自己的智能指针时,可以使用 core::ptr::drop_in_place

一些示例:

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);

impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("{}", self.0);
    }
}

let mut overwritten = PrintOnDrop("drops when overwritten");
overwritten = PrintOnDrop("drops when scope ends");

let tuple = (PrintOnDrop("Tuple first"), PrintOnDrop("Tuple second"));

let moved;
// No destructor run on assignment.
moved = PrintOnDrop("Drops when moved");
// Drops now, but is then uninitialized.
moved;

// Uninitialized does not drop.
let uninitialized: PrintOnDrop;

// After a partial move, only the remaining fields are dropped.
let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("forgotten"));
// Perform a partial move, leaving only `partial_move.0` initialized.
core::mem::forget(partial_move.1);
// When partial_move's scope ends, only the first field is dropped.
}

丢弃作用域

每个变量或临时值都与一个丢弃作用域相关联。当控制流离开丢弃作用域时,与该作用域关联的所有变量按声明顺序(对于变量)或创建顺序(对于临时值)的逆序被丢弃。

丢弃作用域可以通过将 forifwhile 表达式替换为使用 matchloopbreak 的等效表达式来确定。

重载运算符与内置运算符没有区别,不考虑绑定模式

给定一个函数或闭包,有以下丢弃作用域:

  • 整个函数
  • 每个块,包括函数主体
    • 对于块表达式,块和表达式的作用域是相同的作用域。
  • match 表达式的每个分支

丢弃作用域嵌套如下。当同时离开多个作用域时(例如从函数返回时),变量从内向外丢弃。

  • 整个函数作用域是最外层的作用域。
  • 函数主体块包含在整个函数的作用域内。
  • 表达式语句中表达式的父级是语句的作用域。
  • let 语句初始化器的父级是 let 语句的作用域。
  • 语句作用域的父级是包含该语句的块的作用域。
  • match 守卫表达式的父级是该守卫所属分支的作用域。
  • match 表达式中 => 之后的表达式的父级是其所在分支的作用域。
  • 分支作用域的父级是其所属的 match 表达式的作用域。
  • 所有其他作用域的父级是直接封闭表达式的作用域。

函数参数的作用域

所有函数参数都在整个函数主体的作用域内,因此在求值函数时最后丢弃。每个实际函数参数在该参数模式中引入的任何绑定之后丢弃。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
// Drops `y`, then the second parameter, then `x`, then the first parameter
fn patterns_in_parameters(
    (x, _): (PrintOnDrop, PrintOnDrop),
    (_, y): (PrintOnDrop, PrintOnDrop),
) {}

// drop order is 3 2 0 1
patterns_in_parameters(
    (PrintOnDrop("0"), PrintOnDrop("1")),
    (PrintOnDrop("2"), PrintOnDrop("3")),
);
}

局部变量的作用域

let 语句中声明的局部变量与包含 let 语句的块的作用域相关联。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
let declared_first = PrintOnDrop("Dropped last in outer scope");
{
    let declared_in_block = PrintOnDrop("Dropped in inner scope");
}
let declared_last = PrintOnDrop("Dropped first in outer scope");
}

match 表达式或模式匹配 match 守卫中声明的局部变量与其声明所在的 match 分支的分支作用域相关联。

#![allow(unused)]
fn main() {
#![allow(irrefutable_let_patterns)]
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
match PrintOnDrop("Dropped last in the first arm's scope") {
    // When guard evaluation succeeds, control-flow stays in the arm and
    // values may be moved from the scrutinee into the arm's bindings,
    // causing them to be dropped in the arm's scope.
    x if let y = PrintOnDrop("Dropped second in the first arm's scope")
        && let z = PrintOnDrop("Dropped first in the first arm's scope") =>
    {
        let declared_in_block = PrintOnDrop("Dropped in inner scope");
        // Pattern-matching guards' bindings and temporaries are dropped in
        // reverse order, dropping each guard condition operand's bindings
        // before its temporaries. Lastly, variables bound by the arm's
        // pattern are dropped.
    }
    _ => unreachable!(),
}

match PrintOnDrop("Dropped in the enclosing temporary scope") {
    // When guard evaluation fails, control-flow leaves the arm scope,
    // causing bindings and temporaries from earlier pattern-matching
    // guard condition operands to be dropped. This occurs before evaluating
    // the next arm's guard or body.
    _ if let y = PrintOnDrop("Dropped in the first arm's scope")
        && false => unreachable!(),
    // When a guard is executed multiple times due to self-overlapping
    // or-patterns, control-flow leaves the arm scope when the guard fails
    // and re-enters the arm scope before executing the guard again.
    _ | _ if let y = PrintOnDrop("Dropped in the second arm's scope twice")
        && false => unreachable!(),
    _ => {},
}
}

模式中的变量按模式内声明的逆序丢弃。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
let (declared_first, declared_last) = (
    PrintOnDrop("Dropped last"),
    PrintOnDrop("Dropped first"),
);
}

出于丢弃顺序的目的,或模式按第一个子模式给出的顺序声明绑定。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
// Drops `x` before `y`.
fn or_pattern_drop_order<T>(
    (Ok([x, y]) | Err([y, x])): Result<[T; 2], [T; 2]>
//   ^^^^^^^^^^   ^^^^^^^^^^^ This is the second subpattern.
//   |
//   This is the first subpattern.
//
//   In the first subpattern, `x` is declared before `y`. Since it is
//   the first subpattern, that is the order used even if the second
//   subpattern, where the bindings are declared in the opposite
//   order, is matched.
) {}

// Here we match the first subpattern, and the drops happen according
// to the declaration order in the first subpattern.
or_pattern_drop_order(Ok([
    PrintOnDrop("Declared first, dropped last"),
    PrintOnDrop("Declared last, dropped first"),
]));

// Here we match the second subpattern, and the drops still happen
// according to the declaration order in the first subpattern.
or_pattern_drop_order(Err([
    PrintOnDrop("Declared last, dropped first"),
    PrintOnDrop("Declared first, dropped last"),
]));
}

临时作用域

表达式的临时作用域是用于保存该表达式在位置上下文中使用时结果的临时变量的作用域,除非它被提升

除了生命周期扩展外,表达式的临时作用域是包含该表达式的最小作用域,是以下之一:

Note

match 表达式的审查者不是临时作用域,因此审查者中的临时值可以在 match 表达式之后被丢弃。例如,match 1 { ref mut z => z };1 的临时值存活到语句结束。

Note

解构赋值的脱糖限制了其赋值值操作数(RHS)的临时作用域。有关详细信息,请参阅 expr.assign.destructure.tmp-scopes

2024 Edition differences

2024 版本添加了两个新的临时作用域缩小规则:if let 临时值在 else 块之前丢弃,块的尾部表达式的临时值在尾部表达式求值后立即丢弃。

一些示例:

#![allow(unused)]
fn main() {
#![allow(irrefutable_let_patterns)]
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
let local_var = PrintOnDrop("local var");

// Dropped once the condition has been evaluated
if PrintOnDrop("If condition").0 == "If condition" {
    // Dropped at the end of the block
    PrintOnDrop("If body").0
} else {
    unreachable!()
};

if let "if let scrutinee" = PrintOnDrop("if let scrutinee").0 {
    PrintOnDrop("if let consequent").0
    // `if let consequent` dropped here
}
// `if let scrutinee` is dropped here
else {
    PrintOnDrop("if let else").0
    // `if let else` dropped here
};

while let x = PrintOnDrop("while let scrutinee").0 {
    PrintOnDrop("while let loop body").0;
    break;
    // `while let loop body` dropped here.
    // `while let scrutinee` dropped here.
}

// Dropped before the first ||
(PrintOnDrop("first operand").0 == ""
// Dropped before the )
|| PrintOnDrop("second operand").0 == "")
// Dropped before the ;
|| PrintOnDrop("third operand").0 == "";

// Scrutinee is dropped at the end of the function, before local variables
// (because this is the tail expression of the function body block).
match PrintOnDrop("Matched value in final expression") {
    // Non-pattern-matching guards' temporaries are dropped once the
    // condition has been evaluated
    _ if PrintOnDrop("guard condition").0 == "" => (),
    // Pattern-matching guards' temporaries are dropped when leaving the
    // arm's scope
    _ if let "guard scrutinee" = PrintOnDrop("guard scrutinee").0 => {
        let _ = &PrintOnDrop("lifetime-extended temporary in inner scope");
        // `lifetime-extended temporary in inner scope` is dropped here
    }
    // `guard scrutinee` is dropped here
    _ => (),
}
}

操作数

还会创建临时值来保存表达式操作数的结果,同时求值其他操作数。临时值与具有该操作数的表达式的作用域相关联。由于一旦表达式求值完成就会从临时值移出,除非表达式的操作数之一跳出表达式、返回或 panics,否则丢弃它们没有效果。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
loop {
    // Tuple expression doesn't finish evaluating so operands drop in reverse order
    (
        PrintOnDrop("Outer tuple first"),
        PrintOnDrop("Outer tuple second"),
        (
            PrintOnDrop("Inner tuple first"),
            PrintOnDrop("Inner tuple second"),
            break,
        ),
        PrintOnDrop("Never created"),
    );
}
}

常量提升

当表达式可以在常量中编写并借用,并且该借用可以在表达式最初编写的位置被解引用而不改变运行时行为时,值表达式提升到 'static 槽位。也就是说,提升的表达式可以在编译时求值,结果值不包含内部可变性析构器(这些属性尽可能基于值确定,例如 &None 始终具有类型 &'static Option<_>,因为它不包含任何不允许的内容)。

临时生命周期扩展

Note

临时生命周期扩展的确切规则可能会更改。这仅描述当前行为。

let 语句中表达式的临时作用域有时会扩展到包含 let 语句的块的作用域。当通常的临时作用域太小时,基于某些语法规则执行此操作。例如:

#![allow(unused)]
fn main() {
let x = &mut 0;
// Usually a temporary would be dropped by now, but the temporary for `0` lives
// to the end of the block.
println!("{}", x);
}

生命周期扩展也适用于 staticconst 项,其中它使临时值存活到程序结束。例如:

#![allow(unused)]
fn main() {
const C: &Vec<i32> = &Vec::new();
// Usually this would be a dangling reference as the `Vec` would only
// exist inside the initializer expression of `C`, but instead the
// borrow gets lifetime-extended so it effectively has `'static` lifetime.
println!("{:?}", C);
}

如果借用解引用字段元组索引表达式具有扩展的临时作用域,则其操作数也是如此。如果索引表达式具有扩展的临时作用域,则被索引的表达式也具有扩展的临时作用域。

基于模式的扩展

扩展模式是以下之一:

  • 通过引用或可变引用绑定的标识符模式

    #![allow(unused)]
    fn main() {
    fn temp() {}
    let ref x = temp(); // Binds by reference.
    x;
    let ref mut x = temp(); // Binds by mutable reference.
    x;
    }
  • 结构体元组元组结构体切片或模式,其中至少一个直接子模式是扩展模式。

    #![allow(unused)]
    fn main() {
    use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
    static X: AtomicU64 = AtomicU64::new(0);
    struct W<T>(T);
    impl<T> Drop for W<T> { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
    let W { 0: ref x } = W(()); // Struct pattern.
    x;
    let W(ref x) = W(()); // Tuple struct pattern.
    x;
    let (W(ref x),) = (W(()),); // Tuple pattern.
    x;
    let [W(ref x), ..] = [W(())]; // Slice pattern.
    x;
    let (Ok(W(ref x)) | Err(&ref x)) = Ok(W(())); // Or pattern.
    x;
    //
    // All of the temporaries above are still live here.
    assert_eq!(0, X.load(Relaxed));
    }

所以 ref xV(ref x)[ref x, y] 都是扩展模式,但 x&ref x&(ref x,) 不是。

如果 let 语句中的模式是扩展模式,则初始化器表达式的临时作用域被扩展。

#![allow(unused)]
fn main() {
fn temp() {}
// This is an extending pattern, so the temporary scope is extended.
let ref x = *&temp(); // OK
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// This is neither an extending pattern nor an extending expression,
// so the temporary is dropped at the semicolon.
let &ref x = *&&temp(); // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// This is not an extending pattern but it is an extending expression,
// so the temporary lives beyond the `let` statement.
let &ref x = &*&temp(); // OK
x;
}

基于表达式的扩展

对于具有初始化器的 let 语句,扩展表达式是以下之一的表达式:

Note

解构赋值的脱糖使其赋值值操作数(RHS)成为新引入的块中的扩展表达式。有关详细信息,请参阅 expr.assign.destructure.tmp-ext

所以 &mut 0(&1, &mut 2)Some(&mut 3) 中的借用表达式都是扩展表达式。&0 + &1f(&mut 0) 中的借用不是。

扩展借用表达式的操作数的临时作用域扩展

扩展超级宏调用表达式的超级临时值作用域扩展

Note

rustc 不将扩展数组表达式的数组重复操作数视为扩展表达式。是否应该这样做是一个开放问题。

有关详细信息,请参阅 Rust issue #146092

示例

以下是一些表达式具有扩展临时作用域的示例:

#![allow(unused)]
fn main() {
use core::pin::pin;
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
static X: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)] struct S;
impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
const fn temp() -> S { S }
let x = &temp(); // Operand of borrow.
x;
let x = &raw const *&temp(); // Operand of raw borrow.
assert_eq!(X.load(Relaxed), 0);
let x = &temp() as &dyn Send; // Operand of cast.
x;
let x = (&*&temp(),); // Operand of tuple constructor.
x;
struct W<T>(T);
let x = W(&temp()); // Argument to tuple struct constructor.
x;
let x = Some(&temp()); // Argument to tuple enum variant constructor.
x;
let x = { [Some(&temp())] }; // Final expr of block.
x;
let x = const { &temp() }; // Final expr of `const` block.
x;
let x = unsafe { &temp() }; // Final expr of `unsafe` block.
x;
let x = if true { &temp() } else { &temp() };
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//           Final exprs of `if`/`else` blocks.
x;
let x = match () { _ => &temp() }; // `match` arm expression.
x;
let x = pin!(temp()); // Super operand of super macro call expression.
x;
let x = pin!({ &mut temp() }); // As above.
x;
let x = format_args!("{:?}", temp()); // As above.
x;
//
// All of the temporaries above are still live here.
assert_eq!(0, X.load(Relaxed));
}

以下是一些表达式没有扩展临时作用域的示例:

#![allow(unused)]
fn main() {
fn temp() {}
// Arguments to function calls are not extending expressions. The
// temporary is dropped at the semicolon.
let x = core::convert::identity(&temp()); // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
trait Use { fn use_temp(&self) -> &Self { self } }
impl Use for () {}
// Receivers of method calls are not extending expressions.
let x = (&temp()).use_temp(); // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Scrutinees of match expressions are not extending expressions.
let x = match &temp() { x => x }; // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Final expressions of `async` blocks are not extending expressions.
let x = async { &temp() }; // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Final expressions of closures are not extending expressions.
let x = || &temp(); // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Operands of loop breaks are not extending expressions.
let x = loop { break &temp() }; // ERROR
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Operands of breaks to labels are not extending expressions.
let x = 'a: { break 'a &temp() }; // ERROR
x;
}
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// The argument to `pin!` is only an extending expression if the call
// is an extending expression. Since it's not, the inner block is not
// an extending expression, so the temporaries in its trailing
// expression are dropped immediately.
pin!({ &temp() }); // ERROR
}
#![allow(unused)]
fn main() {
fn temp() {}
// As above.
format_args!("{:?}", { &temp() }); // ERROR
}

不运行析构器

手动抑制析构器

core::mem::forget 可用于防止变量的析构器被运行,core::mem::ManuallyDrop 提供了一个包装器来防止变量或字段被自动丢弃。

Note

通过 core::mem::forget 或其他方式防止析构器运行是安全的,即使其类型不是 'static。除了本文档定义的保证运行析构器的位置外,类型可能不会安全地依赖析构器运行来保证健全性。

不展开的进程终止

有几种方法可以在不展开的情况下终止进程,在这种情况下不会运行析构器。

标准库提供 std::process::exitstd::process::abort 来显式执行此操作。此外,如果panic 处理器设置为 abort,panicking 将始终终止进程而不运行析构器。

还有一个需要注意的额外情况:当 panic 到达非展开 ABI 边界时,要么不运行析构器,要么运行直到 ABI 边界的所有析构器。

生命周期省略(Lifetime Elision)

Rust 有规则允许在编译器可以推断出合理默认选择的各种地方省略生命周期。

函数中的生命周期省略

为了使常见模式更符合人体工程学,可以在函数项函数指针闭包 trait签名中省略生命周期参数。以下规则用于推断省略生命周期的生命周期参数。

省略无法推断的生命周期参数是错误的。

占位符生命周期 '_ 也可以用于以相同方式推断生命周期。对于路径中的生命周期,首选使用 '_

Trait 对象生命周期遵循下面讨论的不同规则。

  • 参数中的每个省略生命周期都成为一个不同的生命周期参数。
  • 如果参数中恰好使用了一个生命周期(省略与否),则该生命周期被分配给所有省略的输出生命周期。

在方法签名中有另一个规则

  • 如果接收者类型为 &Self&mut Self,则该对 Self 的引用的生命周期被分配给所有省略的输出生命周期参数。

示例:

#![allow(unused)]
fn main() {
trait T {}
trait ToCStr {}
struct Thing<'a> {f: &'a i32}
struct Command;

trait Example {
fn print1(s: &str);                                   // elided
fn print2(s: &'_ str);                                // also elided
fn print3<'a>(s: &'a str);                            // expanded

fn debug1(lvl: usize, s: &str);                       // elided
fn debug2<'a>(lvl: usize, s: &'a str);                // expanded

fn substr1(s: &str, until: usize) -> &str;            // elided
fn substr2<'a>(s: &'a str, until: usize) -> &'a str;  // expanded

fn get_mut1(&mut self) -> &mut dyn T;                 // elided
fn get_mut2<'a>(&'a mut self) -> &'a mut dyn T;       // expanded

fn args1<T: ToCStr>(&mut self, args: &[T]) -> &mut Command;                  // elided
fn args2<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // expanded

fn other_args1<'a>(arg: &str) -> &'a str;             // elided
fn other_args2<'a, 'b>(arg: &'b str) -> &'a str;      // expanded

fn new1(buf: &mut [u8]) -> Thing<'_>;                 // elided - preferred
fn new2(buf: &mut [u8]) -> Thing;                     // elided
fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>;          // expanded
}

type FunPtr1 = fn(&str) -> &str;                      // elided
type FunPtr2 = for<'a> fn(&'a str) -> &'a str;        // expanded

type FunTrait1 = dyn Fn(&str) -> &str;                // elided
type FunTrait2 = dyn for<'a> Fn(&'a str) -> &'a str;  // expanded
}
#![allow(unused)]
fn main() {
// The following examples show situations where it is not allowed to elide the
// lifetime parameter.

trait Example {
// Cannot infer, because there are no parameters to infer from.
fn get_str() -> &str;                                 // ILLEGAL

// Cannot infer, ambiguous if it is borrowed from the first or second parameter.
fn frob(s: &str, t: &str) -> &str;                    // ILLEGAL
}
}

默认 trait 对象生命周期

trait 对象持有的引用的假定生命周期称为其_默认对象生命周期约束_。这些在 RFC 599 中定义,并在 RFC 1156 中修订。

当生命周期约束被完全省略时,使用这些默认对象生命周期约束代替上面定义的生命周期参数省略规则。

如果 '_ 用作生命周期约束,则约束遵循通常的省略规则。

如果 trait 对象用作泛型类型的类型参数,则首先使用包含类型来尝试推断约束。

  • 如果包含类型有唯一的约束,则该约束是默认值。
  • 如果包含类型有多个约束,则必须指定显式约束。

如果这些规则都不适用,则使用 trait 上的约束:

  • 如果 trait 定义时具有单个生命周期_约束_,则使用该约束。
  • 如果任何生命周期约束使用 'static,则使用 'static
  • 如果 trait 没有生命周期约束,则在表达式中推断生命周期,在表达式外部为 'static
#![allow(unused)]
fn main() {
// For the following trait...
trait Foo { }

// These two are the same because Box<T> has no lifetime bound on T
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;

// ...and so are these:
impl dyn Foo {}
impl dyn Foo + 'static {}

// ...so are these, because &'a T requires T: 'a
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);

// std::cell::Ref<'a, T> also requires T: 'a, so these are the same
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
}
#![allow(unused)]
fn main() {
// This is an example of an error.
trait Foo { }
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
    f1: &'a i32,
    f2: &'b i32,
    f3: T,
}
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
//                                  ^^^^^^^
// Error: the lifetime bound for this object type cannot be deduced from context
}

请注意,最内层对象设置约束,因此 &'a Box<dyn Foo> 仍然是 &'a Box<dyn Foo + 'static>

#![allow(unused)]
fn main() {
// For the following trait...
trait Bar<'a>: 'a { }

// ...these two are the same:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;

// ...and so are these:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
}

conststatic 省略

引用类型的常量静态声明都具有隐式 'static 生命周期,除非指定了显式生命周期。因此,上面涉及 'static 的常量声明可以不带生命周期编写。

#![allow(unused)]
fn main() {
// STRING: &'static str
const STRING: &str = "bitstring";

struct BitsNStrings<'a> {
    mybits: [u32; 2],
    mystring: &'a str,
}

// BITS_N_STRINGS: BitsNStrings<'static>
const BITS_N_STRINGS: BitsNStrings<'_> = BitsNStrings {
    mybits: [1, 2],
    mystring: STRING,
};
}

请注意,如果 staticconst 项包含函数或闭包引用(它们本身包含引用),编译器将首先尝试标准省略规则。如果无法通过其通常规则解析生命周期,则会产生错误。例如:

#![allow(unused)]
fn main() {
struct Foo;
struct Bar;
struct Baz;
fn somefunc(a: &Foo, b: &Bar, c: &Baz) -> usize {42}
// Resolved as `for<'a> fn(&'a str) -> &'a str`.
const RESOLVED_SINGLE: fn(&str) -> &str = |x| x;

// Resolved as `for<'a, 'b, 'c> Fn(&'a Foo, &'b Bar, &'c Baz) -> usize`.
const RESOLVED_MULTIPLE: &dyn Fn(&Foo, &Bar, &Baz) -> usize = &somefunc;
}
#![allow(unused)]
fn main() {
struct Foo;
struct Bar;
struct Baz;
fn somefunc<'a,'b>(a: &'a Foo, b: &'b Bar) -> &'a Baz {unimplemented!()}
// There is insufficient information to bound the return reference lifetime
// relative to the argument lifetimes, so this is an error.
const RESOLVED_STATIC: &dyn Fn(&Foo, &Bar) -> &Baz = &somefunc;
//                                            ^
// this function's return type contains a borrowed value, but the signature
// does not say whether it is borrowed from argument 1 or argument 2
}

特殊类型和 trait

标准库(Standard Library)中存在的某些类型和 trait 是 Rust 编译器已知的。本章记录了这些类型和 trait 的特殊功能。

Box<T>

Box<T> 具有一些 Rust 目前不允许用户定义类型具有的特殊功能。

  • 方法可以将 Box<Self> 作为接收者。
  • 可以在与 T 相同的 crate 中为 Box<T> 实现 trait,而孤立规则阻止为其他泛型类型这样做。

Rc<T>

方法可以将 Rc<Self> 作为接收者。

Arc<T>

方法可以将 Arc<Self> 作为接收者。

Pin<P>

方法可以将 Pin<P> 作为接收者。

UnsafeCell<T>

std::cell::UnsafeCell<T> 用于内部可变性。它确保编译器不会执行对此类类型不正确的优化。

它还确保具有内部可变性类型的 [static 项]不会被放置在标记为只读的内存中。

PhantomData<T>

std::marker::PhantomData<T> 是一个零大小、最小对齐的类型,在型变丢弃检查自动 trait 方面被视为拥有一个 T

运算符 trait

std::opsstd::cmp 中的 trait 用于重载运算符索引表达式调用表达式

DerefDerefMut

除了重载一元 * 运算符外,DerefDerefMut 还用于方法解析解引用强制转换

Drop

Drop trait 提供了析构器,在该类型的值即将被销毁时运行。

Copy

Copy trait 改变了实现它的类型的语义。

实现 Copy 的类型的值在赋值时被复制而不是移动。

Copy 只能为未实现 Drop 且其字段全部为 Copy 的类型实现。对于枚举,这意味着所有变体的所有字段都必须是 Copy。对于联合体,这意味着所有变体都必须是 Copy

Copy 由编译器为以下类型实现:

  • 不捕获值或只捕获 Copy 类型值的闭包

Clone

Clone trait 是 Copy 的超级 trait,因此也需要编译器生成的实现。

编译器为以下类型实现了它:

  • 具有内置 Copy 实现的类型(见上文)
  • 只捕获 Clone 类型值或不从环境捕获值的闭包

Send

Send trait 表示该类型的值可以安全地从一个线程发送到另一个线程。

Sync

Sync trait 表示该类型的值可以安全地在多个线程之间共享。

所有用于不可变 [static 项]的类型都必须实现此 trait。

Termination

Termination trait 表示 main 函数测试函数的可接受返回类型。

自动 trait

SendSyncUnpinUnwindSafeRefUnwindSafe trait 是_自动 trait_。自动 trait 具有特殊属性。

如果没有为给定类型的自动 trait 编写出显式实现或否定实现,则编译器根据以下规则自动实现它:

  • 如果 T 实现了该 trait,则 &T&mut T*const T*mut T[T; n][T] 也实现该 trait。
  • 函数项类型和函数指针自动实现该 trait。
  • 如果所有字段都实现了该 trait,则结构体、枚举、联合体和元组也实现该 trait。
  • 如果所有捕获的类型都实现了该 trait,则闭包也实现该 trait。通过共享引用捕获 T 并按值捕获 U 的闭包实现了 &TU 都实现的所有自动 trait。

对于泛型类型(将上面的内置类型视为对 T 泛型),如果泛型实现可用,则编译器不会为可以使用该实现但不满足必需 trait 约束的类型自动实现它。例如,标准库为所有 TSync&T 实现了 Send;这意味着如果 TSend 但不是 Sync,编译器将不会为 &T 实现 Send

自动 trait 也可以有否定实现,在标准库文档中显示为 impl !AutoTrait for T,它们覆盖自动实现。例如 *mut TSend 的否定实现,因此 *mut T 不是 Send,即使 T 是。目前没有稳定的方式来指定额外的否定实现;它们只存在于标准库中。

自动 trait 可以作为额外的约束添加到任何 trait 对象,即使通常只允许一个 trait。例如,Box<dyn Debug + Send + UnwindSafe> 是有效的类型。

Sized

Sized trait 表示该类型的大小在编译时已知;即它不是动态大小类型

类型参数(trait 中的 Self 除外)默认是 Sized 的,关联类型也是如此。

Sized 始终由编译器自动实现,而不是由实现项实现。

这些隐式 Sized 约束可以通过使用特殊的 ?Sized 约束来放宽。

名称

实体(Entity)是一种语言构造,可以在源程序中以某种方式引用,通常通过路径。实体包括类型泛型参数变量绑定循环标签生命周期字段属性lints

声明(Declaration)是一种语法构造,可以引入名称来引用实体。实体名称在作用域内有效 — 该名称可以被引用的源文本区域。

一些实体在源代码中被显式声明,一些作为语言或编译器扩展的一部分被隐式声明

路径 用于引用实体,可能在另一个模块或类型中。

生命周期和循环标签使用专用语法,使用前导引号。

名称被分成不同的命名空间,允许不同命名空间中的实体共享相同的名称而不冲突。

名称解析 是将路径、标识符和标签绑定到实体声明的编译时过程。

对某些名称的访问可能会根据其可见性受到限制。

显式声明的实体

在源代码中显式引入名称的实体有:

  • [macro_use 属性]可以从另一个 crate 引入宏名称
  • [macro_export 属性]可以在 crate 根中为宏引入别名

此外,宏调用属性可以通过展开为上述项之一来引入名称。

隐式声明的实体

以下实体由语言隐式定义,或由编译器选项和扩展引入:

此外,crate 根模块没有名称,但可以通过某些路径限定符或别名引用。

命名空间

命名空间是声明的名称的逻辑分组。名称根据其引用的实体类型被分成不同的命名空间。命名空间允许一个命名空间中的名称出现与另一个命名空间中的同名名称不冲突。

有几个不同的命名空间,每个包含不同类型的实体。名称的使用将根据上下文在不同的命名空间中查找该名称的声明,如名称解析章节中所述。

以下是命名空间列表及其对应的实体:

不同命名空间中重叠名称可以无歧义使用的示例:

#![allow(unused)]
fn main() {
// Foo introduces a type in the type namespace and a constructor in the value
// namespace.
struct Foo(u32);

// The `Foo` macro is declared in the macro namespace.
macro_rules! Foo {
    () => {};
}

// `Foo` in the `f` parameter type refers to `Foo` in the type namespace.
// `'Foo` introduces a new lifetime in the lifetime namespace.
fn example<'Foo>(f: Foo) {
    // `Foo` refers to the `Foo` constructor in the value namespace.
    let ctor = Foo;
    // `Foo` refers to the `Foo` macro in the macro namespace.
    Foo!{}
    // `'Foo` introduces a label in the label namespace.
    'Foo: loop {
        // `'Foo` refers to the `'Foo` lifetime parameter, and `Foo`
        // refers to the type namespace.
        let x: &'Foo Foo;
        // `'Foo` refers to the label.
        break 'Foo;
    }
}
}

没有命名空间的命名实体

以下实体具有显式名称,但名称不是任何特定命名空间的一部分。

字段

尽管结构体、枚举和联合体字段有名称,但命名字段不存在于显式命名空间中。它们只能通过字段表达式访问,该表达式只检查被访问的特定类型的字段名称。

Use 声明

Use 声明具有它导入到作用域中的命名别名,但 use 项本身不属于特定命名空间。相反,它可以根据被导入的项类型将别名引入多个命名空间。

子命名空间

宏命名空间分为两个子命名空间:一个用于叹号式宏,一个用于属性。当解析属性时,将忽略作用域中的任何叹号式宏。反之,解析叹号式宏将忽略作用域中的属性宏。这防止了一种样式遮蔽另一种样式。

例如,[cfg 属性]和 [cfg 宏]是宏命名空间中具有相同名称的两个不同实体,但它们仍然可以在各自的上下文中使用。

Note

use 导入仍然不能在模块或块中创建同名的重复绑定,无论子命名空间如何。

#[macro_export]
macro_rules! mymac {
    () => {};
}

use myattr::mymac; // error[E0252]: the name `mymac` is defined multiple times.

作用域

作用域是可以用该名称引用命名实体的源文本区域。以下部分提供了作用域规则和行为的详细信息,这些取决于实体的类型及其声明位置。名称如何解析为实体的过程在名称解析章节中描述。有关用于运行析构器的“丢弃作用域“的更多信息,请参阅析构器章节。

项作用域

直接在模块中声明的的名称具有从模块开始到模块结束的作用域。这些项也是模块的成员,可以通过从其模块开始的路径引用。

作为语句声明的项的名称具有从项语句所在的块开始到块结束的作用域。

在同一模块或块中引入与另一个项同名的项是错误的。星号 glob 导入在处理重复名称和遮蔽方面具有特殊行为,有关更多详细信息,请参阅链接章节。

模块中的项可以遮蔽 prelude 中的项。

来自外部模块的项名称在嵌套模块中不在作用域内。可以使用路径引用另一个模块中的项。

关联项作用域

关联项没有作用域,只能通过使用从其关联的类型或 trait 开始的路径引用。方法也可以通过调用表达式引用。

类似于模块或块中的项,在 trait 或实现中引入与同一命名空间中 trait 或 impl 中的另一个项同名的项是错误的。

模式绑定作用域

局部变量模式绑定的作用域取决于其使用位置:

  • let 语句绑定范围从 let 语句之后到其声明所在的块结束。
  • for 绑定在循环主体内。
  • match 守卫 let绑定在后续守卫条件和 match 分支表达式中有效。

局部变量作用域不扩展到项声明中。

模式绑定遮蔽

模式绑定允许遮蔽作用域中的任何名称,但以下情况是错误的:

以下示例说明了局部绑定如何遮蔽项声明:

#![allow(unused)]
fn main() {
fn shadow_example() {
    // Since there are no local variables in scope yet, this resolves to the function.
    foo(); // prints `function`
    let foo = || println!("closure");
    fn foo() { println!("function"); }
    // This resolves to the local closure since it shadows the item.
    foo(); // prints `closure`
}
}

泛型参数作用域

泛型参数在 GenericParams 列表中声明。泛型参数的作用域在声明它的项内。

所有参数在泛型参数列表中都在作用域内,无论它们声明的顺序如何。以下显示了一些参数可能在声明之前被引用的示例:

#![allow(unused)]
fn main() {
// The 'b bound is referenced before it is declared.
fn params_scope<'a: 'b, 'b>() {}

trait SomeTrait<const Z: usize> {}
// The const N is referenced in the trait bound before it is declared.
fn f<T: SomeTrait<N>, const N: usize>() {}
}

泛型参数在类型约束和 where 子句中也在作用域内,例如:

#![allow(unused)]
fn main() {
trait SomeTrait<'a, T> {}
// The <'a, U> for `SomeTrait` refer to the 'a and U parameters of `bounds_scope`.
fn bounds_scope<'a, T: SomeTrait<'a, U>, U>() {}

fn where_scope<'a, T, U>()
    where T: SomeTrait<'a, U>
{}
}

在函数内声明的引用其外部作用域中的泛型参数是错误的。

#![allow(unused)]
fn main() {
fn example<T>() {
    fn inner(x: T) {} // ERROR: can't use generic parameters from outer function
}
}

泛型参数遮蔽

遮蔽泛型参数是错误的,但函数内声明的项允许遮蔽函数的泛型参数名称是例外。

#![allow(unused)]
fn main() {
fn example<'a, T, const N: usize>() {
    // Items within functions are allowed to shadow generic parameter in scope.
    fn inner_lifetime<'a>() {} // OK
    fn inner_type<T>() {} // OK
    fn inner_const<const N: usize>() {} // OK
}
}
#![allow(unused)]
fn main() {
trait SomeTrait<'a, T, const N: usize> {
    fn example_lifetime<'a>() {} // ERROR: 'a is already in use
    fn example_type<T>() {} // ERROR: T is already in use
    fn example_const<const N: usize>() {} // ERROR: N is already in use
    fn example_mixed<const T: usize>() {} // ERROR: T is already in use
}
}

生命周期作用域

生命周期参数在 GenericParams 列表和[高阶 trait 约束]higher-ranked trait bounds 中声明。

'static 生命周期和占位生命周期 '_ 具有特殊含义,不能声明为参数。

生命周期泛型参数作用域

常量静态项以及常量上下文只允许 'static 生命周期引用,因此其中不能有其他生命周期在作用域内。关联常量确实允许引用其 trait 或实现中声明的生命周期。

高阶 trait 约束作用域

作为[高阶 trait 约束]higher-ranked trait bound 声明的生命周期参数的作用域取决于其使用的场景。

  • 作为 TypeBoundWhereClauseItem,声明的生命周期在类型和类型约束中都在作用域内。
  • 作为 TraitBound,声明的生命周期在约束类型路径内都在作用域内。
  • 作为 BareFunctionType,声明的生命周期在函数参数和返回类型中都在作用域内。
#![allow(unused)]
fn main() {
trait Trait<'a>{}

fn where_clause<T>()
    // 'a is in scope in both the type and the type bounds.
    where for <'a> &'a T: Trait<'a>
{}

fn bound<T>()
    // 'a is in scope within the bound.
    where T: for <'a> Trait<'a>
{}

struct Example<'a> {
    field: &'a u32
}

// 'a is in scope in both the parameters and return type.
type FnExample = for<'a> fn(x: Example<'a>) -> Example<'a>;
}

Impl trait 限制

Impl trait 类型只能引用函数或实现上声明的生命周期。

#![allow(unused)]
fn main() {
trait Trait1 {
    type Item;
}
trait Trait2<'a> {}

struct Example;

impl Trait1 for Example {
    type Item = Element;
}

struct Element;
impl<'a> Trait2<'a> for Element {}

// The `impl Trait2` here is not allowed to refer to 'b but it is allowed to
// refer to 'a.
fn foo<'a>() -> impl for<'b> Trait1<Item = impl Trait2<'a> + use<'a>> {
    // ...
   Example
}
}

循环标签作用域

循环标签可以由循环表达式声明。循环标签的作用域从声明位置到循环表达式结束。作用域不扩展到闭包异步块const 参数const 上下文和定义的 for 循环的迭代器表达式中。

#![allow(unused)]
fn main() {
'a: for n in 0..3 {
    if n % 2 == 0 {
        break 'a;
    }
    fn inner() {
        // Using 'a here would be an error.
        // break 'a;
    }
}

// The label is in scope for the expression of `while` loops.
'a: while break 'a {}         // Loop does not run.
'a: while let _ = break 'a {} // Loop does not run.

// The label is not in scope in the defining `for` loop:
'a: for outer in 0..5 {
    // This will break the outer loop, skipping the inner loop and stopping
    // the outer loop.
    'a: for inner in { break 'a; 0..1 } {
        println!("{}", inner); // This does not run.
    }
    println!("{}", outer); // This does not run, either.
}

}

循环标签可以遮蔽外部作用域中同名的标签。对标签的引用指的是最近的定义。

#![allow(unused)]
fn main() {
// Loop label shadowing example.
'a: for outer in 0..5 {
    'a: for inner in 0..5 {
        // This terminates the inner loop, but the outer loop continues to run.
        break 'a;
    }
}
}

Prelude 作用域

Preludes 将实体带入每个模块的作用域。这些实体不是模块的成员,但在名称解析期间被隐式查询。

模块中的声明可以遮蔽 prelude 名称。

Prelude 是分层的,如果它们包含同名的实体,则一个会遮蔽另一个。Prelude 可能遮蔽其他 prelude 的顺序如下,其中较早的条目可能遮蔽较晚的条目:

  1. 外部 prelude
  2. 工具 prelude
  3. macro_use prelude
  4. 标准库 prelude
  5. 语言 prelude

macro_rules 作用域

macro_rules 宏的作用域在通过示例定义的宏章节中描述。该行为取决于 macro_usemacro_export 属性的使用。

派生宏辅助属性

派生宏辅助属性在其对应的 [derive 属性]指定的项中在作用域内。作用域从 derive 属性之后到项结束。

辅助属性遮蔽作用域中同名的其他属性。

Self 作用域

虽然 Self 是具有特殊含义的关键字,但它与名称解析的交互方式类似于普通名称。

结构体枚举联合体trait实现定义中的隐式 Self 类型的处理方式类似于泛型参数,并且与泛型类型参数的作用域方式相同。

实现的值命名空间中的隐式 Self 构造函数在实现的主体(实现的关联项)内都在作用域内。

#![allow(unused)]
fn main() {
// Self type within struct definition.
struct Recursive {
    f1: Option<Box<Self>>
}

// Self type within generic parameters.
struct SelfGeneric<T: Into<Self>>(T);

// Self value constructor within an implementation.
struct ImplExample();
impl ImplExample {
    fn example() -> Self { // Self type
        Self() // Self value constructor
    }
}
}

Prelude

Prelude 是一组名称,自动带入 crate 中每个模块的作用域。

这些 prelude 名称不是模块本身的一部分:它们在名称解析期间被隐式查询。例如,即使像 Box 这样的东西在每个模块中都在作用域内,您也不能将其引用为 self::Box,因为它不是当前模块的成员。

有几个不同的 prelude:

标准库 prelude

每个 crate 都有一个标准库 prelude,它由来自单个标准库模块的名称组成。

使用的模块取决于 crate 的版本,以及 [no_std 属性]是否应用于 crate:

Note

core::panic!std::panic! 之一由于[标准库 prelude]被带入作用域,并且用户编写的glob 导入将另一个带入作用域时,rustc 目前允许使用 panic!,即使它是有歧义的。用户编写的 glob 导入优先以解决此歧义。

有关详细信息,请参阅 names.resolution.expansion.imports.ambiguity.panic-hack

外部 prelude

在根模块中使用 extern crate 导入的外部 crate 或提供给编译器的外部 crate(如 rustc--extern 标志)被添加到外部 prelude。如果使用别名导入(如 extern crate orig_name as new_name),则符号 new_name 被添加到 prelude。

core crate 始终添加到外部 prelude。

只要在 crate 根中未指定 [no_std 属性],就会添加 std crate。

2018 Edition differences

在 2015 版本中,外部 prelude 中的 crate 不能通过 use 声明引用,因此通常的做法是包含 extern crate 声明以将它们带入作用域。

从 2018 版本开始,use 声明可以引用外部 prelude 中的 crate,因此使用 extern crate 被认为是不符合习惯的。

Note

rustc 一起提供的其他 crate,如 alloctest,在使用 Cargo 时不会自动包含在 --extern 标志中。即使在 2018 版本中,也必须使用 extern crate 声明将它们带入作用域。

#![allow(unused)]
fn main() {
extern crate alloc;
use alloc::rc::Rc;
}

Cargo 仅对 proc-macro crate 将 proc_macro 带入外部 prelude。

no_std 属性

*no_std 属性*导致 std crate 不被自动链接,并且[标准库 prelude]改为使用 core prelude。

Example

#![no_std]

Note

当 crate 针对不支持标准库的平台或故意不使用标准库的功能时,使用 no_std 很有用。这些功能主要是动态内存分配(例如 BoxVec)以及文件和网络功能(例如 std::fsstd::io)。

Warning

使用 no_std 不会阻止标准库被链接。在 crate 或其依赖项之一中编写 extern crate std 仍然有效;这将导致编译器将 std crate 链接到程序中。

no_std 属性使用 MetaWord 语法。

no_std 属性只能应用于 crate 根。

no_std 属性可以在一个形式上使用任意次数。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

no_std 属性将[标准库 prelude]更改为使用 core prelude 而不是 std prelude。

2018 Edition differences

在 2018 版本之前,默认将 std 注入到 crate 根中。如果指定了 no_std,则注入 core。从 2018 版本开始,无论是否指定 no_std,都不会注入到 crate 根中。

语言 prelude

语言 prelude 包含内置到语言中的类型和属性的名称。语言 prelude 始终在作用域内。

它包含以下内容:

macro_use prelude

macro_use prelude 包含通过应用于 extern crate 的 [macro_use 属性]从外部 crate 导入的宏。

工具 prelude

工具 prelude 包含类型命名空间中外部工具的工具名称。有关更多详细信息,请参阅工具属性部分。

no_implicit_prelude 属性

*no_implicit_prelude [属性]*用于阻止隐式 prelude 被带入作用域。

Example

#![allow(unused)]
fn main() {
// The attribute can be applied to the crate root to affect
// all modules.
#![no_implicit_prelude]

// Or it can be applied to a module to only affect that module
// and its descendants.
#[no_implicit_prelude]
mod example {
    // ...
}
}

no_implicit_prelude 属性使用 MetaWord 语法。

no_implicit_prelude 属性只能应用于 crate 或模块。

Note

rustc 会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。

no_implicit_prelude 属性可以在一个形式上使用任意次数。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

no_implicit_prelude 属性阻止[标准库 prelude]、外部 preludemacro_use prelude工具 prelude被带入模块及其后代的作用域。

Note

尽管有 #![no_implicit_prelude]rustc 目前仍然隐式地将某些宏带入作用域。这些宏是:

例如,这有效:

#![no_implicit_prelude]
fn main() { assert!(true); }

不要依赖此行为;它可能在将来被移除。使用 #![no_implicit_prelude] 时,请始终显式地带入您需要的项。

有关详细信息,请参阅 Rust PR #62086Rust PR #139493

no_implicit_prelude 属性不影响语言 prelude

2018 Edition differences

在 2015 版本中,no_implicit_prelude 属性不影响 macro_use prelude,并且从标准库导出的所有宏仍然包含在 macro_use prelude 中。从 2018 版本开始,该属性确实会移除 macro_use prelude。

路径

路径(Path)是由 :: 词法单元分隔的一个或多个路径段的序列。路径用于引用、值、类型属性

仅包含标识符段的简单路径的两个示例:

x;
x::y::z;

路径类型

简单路径

Syntax
SimplePath
    ::? SimplePathSegment ( :: SimplePathSegment )*

SimplePathSegment
    IDENTIFIER | super | self | crate | $crate

简单路径用于可见性标记、属性use 项中。例如:

#![allow(unused)]
fn main() {
use std::io::{self, Write};
mod m {
    #[clippy::cyclomatic_complexity = "0"]
    pub (in super) fn f1() {}
}
}

表达式中的路径

Syntax
PathInExpression
    ::? PathExprSegment ( :: PathExprSegment )*

PathExprSegment
    PathIdentSegment ( :: GenericArgs )?

PathIdentSegment
    IDENTIFIER | super | self | Self | crate | $crate

GenericArgs
      < GenericArgList? >
    | ( TypeList? ) ( -> TypeNoBounds )?

GenericArgList
    ( GenericArg , )* GenericArg ,?

TypeList
    ( Type , )* Type ,?

GenericArg
    Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds

GenericArgsConst
      BlockExpression
    | LiteralExpression
    | - LiteralExpression
    | SimplePathSegment

GenericArgsBinding
    TypePathSegment = Type

GenericArgsBounds
    TypePathSegment : Bounds

表达式中的路径允许指定带有泛型参数的路径。它们用于表达式模式中的各种位置。

泛型参数的开头 < 之前需要 :: 词法单元,以避免与小于运算符的歧义。这在口语中被称为“turbofish“语法。

#![allow(unused)]
fn main() {
(0..10).collect::<Vec<_>>();
Vec::<u8>::with_capacity(1024);
}

泛型参数的顺序仅限于生命周期参数,然后是类型参数,然后是 const 参数,然后是相等约束。

Const 参数必须用大括号括起来,除非它们是字面量推断 const 或单段路径。推断 const 不能用大括号括起来。

#![allow(unused)]
fn main() {
mod m {
    pub const C: usize = 1;
}
const C: usize = m::C;
fn f<const N: usize>() -> [u8; N] { [0; N] }

let _ = f::<1>(); // Literal.
let _: [_; 1] = f::<_>(); // Inferred const.
let _: [_; 1] = f::<(((_)))>(); // Inferred const.
let _ = f::<C>(); // Single segment path.
let _ = f::<{ m::C }>(); // Multi-segment path must be braced.
}
#![allow(unused)]
fn main() {
fn f<const N: usize>() -> [u8; N] { [0; _] }
let _: [_; 1] = f::<{ _ }>();
//                    ^ ERROR `_` not allowed here
}

Note

在泛型参数列表中,推断 const 被解析为[推断类型]inferred type,但在语义上被视为单独的 const 泛型参数

对应于 impl Trait 类型的合成类型参数是隐式的,不能显式指定。

限定路径

完全限定路径允许消除trait 实现的路径歧义,并指定规范路径。在类型规范中使用时,它支持使用下面指定的类型语法。

#![allow(unused)]
fn main() {
struct S;
impl S {
    fn f() { println!("S"); }
}
trait T1 {
    fn f() { println!("T1 f"); }
}
impl T1 for S {}
trait T2 {
    fn f() { println!("T2 f"); }
}
impl T2 for S {}
S::f();  // Calls the inherent impl.
<S as T1>::f();  // Calls the T1 trait function.
<S as T2>::f();  // Calls the T2 trait function.
}

类型中的路径

Syntax
TypePath::? TypePathSegment ( :: TypePathSegment )*

TypePathSegmentPathIdentSegment ( ::? GenericArgs )?

类型路径用于类型定义、trait 约束和限定路径中。

虽然 :: 词法单元在泛型参数之前是允许的,但它不是必需的,因为不像 PathInExpression 中那样存在歧义。

#![allow(unused)]
fn main() {
mod ops {
    pub struct Range<T> {f1: T}
    pub trait Index<T> {}
    pub struct Example<'a> {f1: &'a i32}
}
struct S;
impl ops::Index<ops::Range<usize>> for S { /*...*/ }
fn i<'a>() -> impl Iterator<Item = ops::Example<'a>> {
    // ...
   const EXAMPLE: Vec<ops::Example<'static>> = Vec::new();
   EXAMPLE.into_iter()
}
type G = std::boxed::Box<dyn std::ops::FnOnce(isize) -> isize>;
}

路径限定符

路径可以用各种前导限定符表示,以更改其解析的含义。

Note

[use 声明]对 selfsupercrate$crate 有额外的行为和限制。

::

:: 开头的路径被视为全局路径,其中路径的段从根据版本不同的位置开始解析。路径中的每个标识符必须解析为一个项。

2018 Edition differences

在 2015 版本中,标识符从“crate 根“(2018 版本中的 crate::)解析,其中包含各种不同的项,包括外部 crate、默认 crate(如 stdcore)以及 crate 顶层的项(包括 use 导入)。

从 2018 版本开始,以 :: 开头的路径从外部 prelude 中的 crate 解析。也就是说,它们后面必须跟 crate 的名称。

#![allow(unused)]
fn main() {
pub fn foo() {
    // In the 2018 edition, this accesses `std` via the extern prelude.
    // In the 2015 edition, this accesses `std` via the crate root.
    let now = ::std::time::Instant::now();
    println!("{:?}", now);
}
}
// 2015 Edition
mod a {
    pub fn foo() {}
}
mod b {
    pub fn foo() {
        ::a::foo(); // call `a`'s foo function
        // In Rust 2018, `::a` would be interpreted as the crate `a`.
    }
}
fn main() {}

self

self 将路径解析为相对于当前模块。

self 只能用作路径的第一段(前面没有 ::)或最后一段(前面有 ::)。

self 作为路径的最后一段出现时,它指的是前一段命名的实体。前面的路径必须解析为模块枚举trait

mod m {
    pub enum E { V1 }
    pub trait Tr {}
    pub(in crate::m::self) fn g() {} // OK: Modules can be parents of `self`.
}
type Ty = m::E::self; // OK: Enumerations can be parents of `self`.
fn f<T: m::Tr::self>() {} // OK: Traits can be parents of `self`.
fn main() { let _: Ty = m::E::V1; }
struct S;
type Ty = S::self; // ERROR: Structs cannot be parents of `self`.
fn main() {}

Note

有关 use 声明中 self 的其他规则,请参阅 items.use.self

在方法主体中,由单个 self 段组成的路径解析为方法的 self 参数。

fn foo() {}
fn bar() {
    self::foo();
}
struct S(bool);
impl S {
  fn baz(self) {
        self.0;
    }
}
fn main() {}

Self

大写“S“的 Self 用于引用当前正在实现或定义的类型。它可以在以下情况下使用:

  • trait 定义中,它指的是实现该 trait 的类型。
  • 结构体枚举联合体的定义中,它指的是被定义的类型。定义不允许无限递归(必须有间接层)。

Self 的作用域行为类似于泛型参数;有关更多详细信息,请参阅 Self 作用域部分。

Self 只能用作第一段,前面没有 ::

Self 路径不能包含泛型参数(如 Self::<i32>)。

#![allow(unused)]
fn main() {
trait T {
    type Item;
    const C: i32;
    // `Self` will be whatever type that implements `T`.
    fn new() -> Self;
    // `Self::Item` will be the type alias in the implementation.
    fn f(&self) -> Self::Item;
}
struct S;
impl T for S {
    type Item = i32;
    const C: i32 = 9;
    fn new() -> Self {           // `Self` is the type `S`.
        S
    }
    fn f(&self) -> Self::Item {  // `Self::Item` is the type `i32`.
        Self::C                  // `Self::C` is the constant value `9`.
    }
}

// `Self` is in scope within the generics of a trait definition,
// to refer to the type being defined.
trait Add<Rhs = Self> {
    type Output;
    // `Self` can also reference associated items of the
    // type being implemented.
    fn add(self, rhs: Rhs) -> Self::Output;
}

struct NonEmptyList<T> {
    head: T,
    // A struct can reference itself (as long as it is not
    // infinitely recursive).
    tail: Option<Box<Self>>,
}
}

super

路径中的 super 解析为父模块。

它只能用作路径的前导段,可能在初始 self 段之后。

mod a {
    pub fn foo() {}
}
mod b {
    pub fn foo() {
        super::a::foo(); // call a's foo function
    }
}
fn main() {}

super 可以在第一个 superself 之后重复多次,以引用祖先模块。

mod a {
    fn foo() {}

    mod b {
        mod c {
            fn foo() {
                super::super::foo(); // call a's foo function
                self::super::super::foo(); // call a's foo function
            }
        }
    }
}
fn main() {}

crate

crate 将路径解析为相对于当前 crate。

crate 只能用作第一段,前面没有 ::

fn foo() {}
mod a {
    fn bar() {
        crate::foo();
    }
}
fn main() {}

$crate

$crate 仅在宏转录器中使用,只能用作第一段,前面没有 ::

$crate 将展开为访问定义宏的 crate 顶层的项的路径,无论宏在哪个 crate 中被调用。

pub fn increment(x: u32) -> u32 {
    x + 1
}

#[macro_export]
macro_rules! inc {
    ($x:expr) => ( $crate::increment($x) )
}
fn main() { }

规范路径

在模块或实现中定义的每个项都有一个规范路径,对应于它在 crate 中的定义位置。

这些项的所有其他路径都是别名。

规范路径定义为路径前缀后跟项本身定义的路径段。

实现use 声明没有规范路径,尽管实现定义的项确实有。块表达式中定义的项没有规范路径。在没有规范路径的模块中定义的项没有规范路径。在引用没有规范路径的项的实现中定义的关联项(例如作为实现类型、被实现的 trait、类型参数或类型参数上的约束)没有规范路径。

模块的路径前缀是该模块的规范路径。

对于裸实现,它是被实现项的规范路径,用尖括号(<>括起来。

对于 trait 实现,它是被实现项的规范路径后跟 as 后跟 trait 的规范路径,全部用尖括号(<>括起来。

规范路径仅在给定 crate 内有意义。跨 crate 没有全局命名空间;项的规范路径仅在 crate 内标识它。

// Comments show the canonical path of the item.

mod a { // crate::a
    pub struct Struct; // crate::a::Struct

    pub trait Trait { // crate::a::Trait
        fn f(&self); // crate::a::Trait::f
    }

    impl Trait for Struct {
        fn f(&self) {} // <crate::a::Struct as crate::a::Trait>::f
    }

    impl Struct {
        fn g(&self) {} // <crate::a::Struct>::g
    }
}

mod without { // crate::without
    fn canonicals() { // crate::without::canonicals
        struct OtherStruct; // None

        trait OtherTrait { // None
            fn g(&self); // None
        }

        impl OtherTrait for OtherStruct {
            fn g(&self) {} // None
        }

        impl OtherTrait for crate::a::Struct {
            fn g(&self) {} // None
        }

        impl crate::a::Trait for OtherStruct {
            fn f(&self) {} // None
        }
    }
}

fn main() {}

名称解析

_名称解析_是将路径和其他标识符绑定到这些实体的声明的过程。名称被分成不同的命名空间,允许不同命名空间中的实体共享相同的名称而不冲突。每个名称在作用域内有效,即可以引用该名称的源文本区域。对名称的访问可能会根据其可见性受到限制。

名称解析在整个编译过程中分为三个阶段。第一阶段,展开时解析,解析所有 [use 声明]和宏调用。第二阶段,主要解析,解析所有尚未解析且不依赖于类型信息来解析的名称。最后阶段,类型相关解析,一旦类型信息可用,就解析剩余的名称。

Note

展开时解析也称为早期解析。主要解析也称为晚期解析

通用

本节中的规则适用于名称解析的所有阶段。

作用域

Note

这是关于各种作用域内名称解析的未来扩展的占位符。

展开时名称解析

展开时名称解析是完成宏展开并完全生成 crate 的 AST 所需的名称解析阶段。此阶段需要解析宏调用和 use 声明。解析 use 声明是通过基于路径的作用域解析的宏调用所必需的。解析宏调用是为了展开它们。

展开时名称解析后,AST 不得包含任何未展开的宏调用。每个宏调用都解析为最终 AST 或外部 crate 中存在的有效定义。

#![allow(unused)]
fn main() {
m!(); // ERROR: Cannot find macro `m` in this scope.
}

名称的解析必须是稳定的。展开后,完全展开的 AST 中的名称必须解析到相同的定义,无论宏展开和导入解析的顺序如何。

在宏展开期间选择的所有名称解析候选都被视为投机性的。一旦 crate 完全展开,所有投机性导入解析都会被验证,以确保宏展开没有引入任何新的歧义。

Note

由于宏展开的迭代性质,这会导致所谓的时空旅行歧义,例如当宏或 glob 导入引入一个与其自身基本路径有歧义的项时。

fn main() {}
macro_rules! f {
    () => {
        mod m {
            pub(crate) use f;
        }
    }
}
f!();

const _: () = {
    // Initially, we speculatively resolve `m` to the module in
    // the crate root.
    //
    // Expansion of `f` introduces a second `m` module inside this
    // body.
    //
    // Expansion-time resolution finalizes resolutions by re-
    // resolving all imports and macro invocations, sees the
    // introduced ambiguity and reports it as an error.
    m::f!(); // ERROR: `m` is ambiguous.
};

导入

所有 use 声明在此解析阶段完全解析。类型相关路径在此阶段无法解析,将产生错误。

#![allow(unused)]
fn main() {
mod m {
    pub const C: () = ();
    pub enum E { V }
    pub type A = E;
    impl E {
        pub const C: () = ();
    }
}

// Valid imports resolved at expansion-time:
use m::C; // OK.
use m::E; // OK.
use m::A; // OK.
use m::E::V; // OK.

// Valid expressions resolved during type-relative resolution:
let _ = m::A::V; // OK.
let _ = m::E::C; // OK.
}
#![allow(unused)]
fn main() {
mod m {
    pub const C: () = ();
    pub enum E { V }
    pub type A = E;
    impl E {
        pub const C: () = ();
    }
}
// Invalid type-relative imports that can't resolve at expansion-time:
use m::A::V; // ERROR: Unresolved import `m::A::V`.
use m::E::C; // ERROR: Unresolved import `m::E::C`.
}

外部作用域中通过 use 声明引入的名称会被来自内部作用域的同名同命名空间候选遮蔽,除非受到名称解析歧义的限制。

#![allow(unused)]
fn main() {
pub mod m1 {
    pub mod ambig {
        pub const C: u8 = 1;
    }
}

pub mod m2 {
    pub mod ambig {
        pub const C: u8 = 2;
    }
}

// This introduces the name `ambig` in the outer scope.
use m1::ambig;
const _: () = {
    // This shadows `ambig` in the inner scope.
    use m2::ambig;
    // The inner candidate is selected here
    // as the resolution of `ambig`.
    use ambig::C;
    assert!(C == 2);
};
}

在以下情况下,允许在单个作用域内遮蔽通过 use 声明引入的名称:

歧义

在展开时解析期间,存在某些情况,其中存在多个宏定义、use 声明或模块,导入或宏调用的名称可能引用它们,但编译器无法一致地确定哪个候选应该遮蔽另一个。在这些情况下不允许遮蔽,编译器会发出歧义错误。

名称不能通过有歧义的 glob 导入解析。只要名称未被使用,glob 导入允许在同一命名空间中导入冲突的名称。来自有歧义 glob 导入的具有冲突候选的名称仍然可以被非 glob 导入遮蔽,并且可以在不产生错误的情况下使用。错误在使用时发生,而不是在导入时。

#![allow(unused)]
fn main() {
mod m1 {
    pub struct Ambig;
}

mod m2 {
    pub struct Ambig;
}

// OK: This brings conficting names in the same namespace into scope
// but they have not been used yet.
use m1::*;
use m2::*;

const _: () = {
    // The error happens when the name with the conflicting candidates
    // is used.
    let x = Ambig; // ERROR: `Ambig` is ambiguous.
};
}
#![allow(unused)]
fn main() {
mod m1 {
    pub struct Ambig;
}

mod m2 {
    pub struct Ambig;
}

use m1::*;
use m2::*; // OK: No name conflict.
const _: () = {
    // This is permitted, since resolution is not through the
    // ambiguous globs.
    struct Ambig;
    let x = Ambig; // OK.
};
}

多个 glob 导入允许导入相同的名称,并且如果导入的是同一个项(遵循重导出),则允许使用该名称。名称的可见性是导入的最大可见性。

mod m1 {
    pub struct Ambig;
}

mod m2 {
    // This reexports the same `Ambig` item from a second module.
    pub use super::m1::Ambig;
}

mod m3 {
    // These both import the same `Ambig`.
    //
    // The visibility of `Ambig` is `pub` because that is the
    // maximum visibility between these two `use` declarations.
    pub use super::m1::*;
    use super::m2::*;
}

mod m4 {
    // `Ambig` can be used through the `m3` globs and still has
    // `pub` visibility.
    pub use crate::m3::Ambig;
}

const _: () = {
    // Therefore, we can use it here.
    let _ = m4::Ambig; // OK.
};
fn main() {}

外部作用域中有另一个候选可用时,导入和宏调用中的名称不能通过 glob 导入解析。

Note

core::panic!std::panic! 之一由于标准库 prelude被带入作用域,并且用户编写的glob 导入将另一个带入作用域时,rustc 目前允许使用 panic!,即使它是有歧义的。用户编写的 glob 导入优先以解决此歧义。

在 Rust 2021 及更高版本中,core::panic!std::panic! 操作相同。但在早期版本中,它们不同;只有 std::panic! 接受 String 作为格式参数。

例如,这是错误:

extern crate core;
use ::core::prelude::v1::*;
fn main() {
    panic!(std::string::String::new()); // ERROR.
}

这是被接受的:

#![no_std]
extern crate std;
use ::std::prelude::v1::*;
fn main() {
    panic!(std::string::String::new()); // OK.
}

不要依赖此行为;计划是移除它。

有关详细信息,请参阅 Rust issue #147319

#![allow(unused)]
fn main() {
mod glob {
    pub mod ambig {
        pub struct Name;
    }
}

// Outer `ambig` candidate.
pub mod ambig {
    pub struct Name;
}

const _: () = {
    // Cannot resolve `ambig` through this glob
    // because of the outer `ambig` candidate above.
    use glob::*;
    use ambig::Name; // ERROR: `ambig` is ambiguous.
};
}
#![allow(unused)]
fn main() {
// As above, but with macros.
pub mod m {
    macro_rules! f {
        () => {};
    }
    pub(crate) use f;
}
pub mod glob {
    macro_rules! f {
        () => {};
    }
    pub(crate) use f as ambig;
}

use m::f as ambig;

const _: () = {
    use glob::*;
    ambig!(); // ERROR: `ambig` is ambiguous.
};
}

Note

这些歧义错误特定于展开时解析。在解析的后期阶段,给定名称有多个候选不被视为错误。只要导入本身没有歧义,总会有一个单一的无歧义最近解析。

#![allow(unused)]
fn main() {
mod glob {
    pub const AMBIG: u8 = 1;
}

mod outer {
    pub const AMBIG: u8 = 2;
}

use outer::AMBIG;

const C: () = {
    use glob::*;
    assert!(AMBIG == 1);
    //      ^---- This `AMBIG` is resolved during primary resolution.
};
}

名称不能通过有歧义的宏重导出解析。当宏重导出会遮蔽外部作用域中同名的文本宏候选时,宏重导出是有歧义的。

#![allow(unused)]
fn main() {
// Textual macro candidate.
macro_rules! ambig {
    () => {}
}

// Path-based macro candidate.
macro_rules! path_based {
    () => {}
}

pub fn f() {
    // This reexport of the `path_based` macro definition
    // as `ambig` may not shadow the `ambig` macro definition
    // which is resolved via textual macro scope.
    use path_based as ambig;
    ambig!(); // ERROR: `ambig` is ambiguous.
}
}

Note

由于编译器中的实现细节,特别是当前的作用域访问逻辑和支持此行为的复杂性,需要此限制。此歧义错误可能在将来被移除。

宏通过遍历可用作用域以找到可用候选来解析。宏分为两个子命名空间,一个用于函数式宏,另一个用于属性和派生。来自错误子命名空间的解析候选被忽略。

可用的作用域类型按以下顺序访问。每种作用域类型代表一个或多个作用域。

Note

编译器将尝试解析在其关联宏将其引入作用域之前使用的派生辅助。此作用域在正确在作用域内的解析派生辅助候选的作用域之后访问。此行为计划被移除。

有关更多信息,请参阅派生辅助作用域

Note

此访问顺序可能在将来更改,例如根据词法作用域交错访问文本和基于路径的作用域候选。

2018 Edition differences

从 2018 版本开始,当存在 #[no_implicit_prelude] 时,不访问 #[macro_use] prelude。

名称 cfgcfg_attr 在宏属性子命名空间中是保留的。

歧义

名称不能通过宏展开内的有歧义候选解析。当宏展开内的候选会遮蔽来自第一个候选的宏展开外部的同名候选,并且被解析的名称的调用也来自第一个候选的宏展开外部时,宏展开内的候选是有歧义的。

#![allow(unused)]
fn main() {
macro_rules! define_ambig {
    () => {
        macro_rules! ambig {
            () => {}
        }
    }
}

// Introduce outer candidate definition for `ambig` macro invocation.
macro_rules! ambig {
    () => {}
}

// Introduce a second candidate definition for `ambig` inside of a
// macro expansion.
define_ambig!();

// The definition of `ambig` from the second invocation
// of `define_ambig` is the innermost canadidate.
//
// The definition of `ambig` from the first invocation of
// `define_ambig` is the second candidate.
//
// The compiler checks that the first candidate is inside of a macro
// expansion, that the second candidate is not from within the same
// macro expansion, and that the name being resolved is not from
// within the same macro expansion.
ambig!(); // ERROR: `ambig` is ambiguous.
}

反向不被视为有歧义。

#![allow(unused)]
fn main() {
macro_rules! define_ambig {
    () => {
        macro_rules! ambig {
            () => {}
        }
    }
}
// Swap order of definitions.
define_ambig!();
macro_rules! ambig {
    () => {}
}
// The innermost candidate is now less expanded so it may shadow more
// the macro expanded definition above it.
ambig!();
}

如果被解析的调用在最内层候选的展开内,也不被视为有歧义。

#![allow(unused)]
fn main() {
macro_rules! ambig {
    () => {}
}

macro_rules! define_and_invoke_ambig {
    () => {
        // Define innermost candidate.
        macro_rules! ambig {
            () => {}
        }

        // Invocation of `ambig` is in the same expansion as the
        // innermost candidate.
        ambig!(); // OK
    }
}

define_and_invoke_ambig!();
}

即使两个定义来自同一个宏的调用,最外层的候选仍然被认为是“较少展开的“,因为它不在包含最内层候选定义的展开内。

#![allow(unused)]
fn main() {
macro_rules! define_ambig {
    () => {
        macro_rules! ambig {
            () => {}
        }
    }
}
define_ambig!();
define_ambig!();
ambig!(); // ERROR: `ambig` is ambiguous.
}

这也适用于导入,只要名称的最内层候选来自宏展开内。

#![allow(unused)]
fn main() {
macro_rules! define_ambig {
    () => {
        mod ambig {
            pub struct Name;
        }
    }
}

mod ambig {
    pub struct Name;
}

const _: () = {
    // Introduce innermost candidate for
    // `ambig` mod in this macro expansion.
    define_ambig!();
    use ambig::Name; // ERROR: `ambig` is ambiguous.
};
}

用户定义的属性或派生宏不能遮蔽内置的非宏属性(例如 inline)。

// with-helper/src/lib.rs
use proc_macro::TokenStream;
#[proc_macro_derive(WithHelperAttr, attributes(non_exhaustive))]
//                                             ^^^^^^^^^^^^^^
//                                   User-defined attribute candidate.
// ...
pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream {
    TokenStream::new()
}
// src/lib.rs
#[derive(with_helper::WithHelperAttr)]
#[non_exhaustive] // ERROR: `non_exhaustive` is ambiguous.
struct S;

Note

无论内置属性是什么名称的候选,这都适用:

// with-helper/src/lib.rs
use proc_macro::TokenStream;

#[proc_macro_derive(WithHelperAttr, attributes(helper))]
//                                             ^^^^^^
//                                 User-defined attribute candidate.
// ...
pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream {
    TokenStream::new()
}
// src/lib.rs
use inline as helper;
//            ^----- Built-in attribute candidate via reexport.

#[derive(with_helper::WithHelperAttr)]
#[helper] // ERROR: `helper` is ambiguous.
struct S;

主要名称解析

Note

这是关于主要名称解析的未来扩展的占位符。

类型相关解析

Note

这是关于类型相关解析的未来扩展的占位符。

可见性和私有性

Syntax
Visibility
      pub
    | pub ( crate )
    | pub ( self )
    | pub ( super )
    | pub ( in SimplePath )

Visibility pub pub ( crate ) pub ( self ) pub ( super ) pub ( in SimplePath )

这两个术语(Visibility 和 Privacy)经常互换使用,它们试图传达的是“这个项可以在这个位置使用吗?“这个问题的答案。

Rust 的名称解析在命名空间的全局层次结构上操作。层次结构中的每一层都可以被认为是某个项。这些项是上面提到的那些,但也包括外部 crate。声明或定义一个新模块可以被认为是将一个新树插入到定义位置的层次结构中。

为了控制接口是否可以跨模块使用,Rust 检查每个项的使用以查看是否应该允许。这就是生成隐私警告的地方,或者“您使用了另一个模块的私有项,并且不被允许。“

默认情况下,一切都是私有的,有两个例外:pub Trait 中的关联项默认是公开的;pub 枚举中的枚举变体也默认是公开的。当一个项被声明为 pub 时,可以认为它对外部世界是可访问的。例如:

fn main() {}
// Declare a private struct
struct Foo;

// Declare a public struct with a private field
pub struct Bar {
    field: i32,
}

// Declare a public enum with two public variants
pub enum State {
    PubliclyAccessibleState,
    PubliclyAccessibleState2,
}

有了项是公开还是私有的概念,Rust 允许在两种情况下进行项访问:

  1. 如果一个项是公开的,那么如果您可以从 m 访问该项的所有祖先模块,则可以从某个模块 m 外部访问它。您也可以通过重导出来命名该项。见下文。
  2. 如果一个项是私有的,它可能被当前模块及其后代访问。

这两种情况对于创建公开 API 同时隐藏内部实现细节的模块层次结构非常强大。为了帮助解释,这里有几个用例以及它们需要什么:

  • 库开发者需要向链接其库的 crate 暴露功能。根据第一种情况的结果,这意味着任何可在外部使用的东西都必须从根到目标项都是 pub。链中的任何私有项都将禁止外部访问。

  • 一个 crate 需要一个全局可用的“辅助模块“,但它不想将辅助模块暴露为公共 API。为了实现这一点,crate 层次结构的根将有一个私有模块,其内部有一个“公共 API“。因为整个 crate 是根的后代,所以整个本地 crate 可以通过第二种情况访问这个私有模块。

  • 当为模块编写单元测试时,一个常见的习惯用法是在被测试模块的直接子模块中命名 mod test。这个模块可以通过第二种情况访问父模块的任何项,这意味着内部实现细节也可以从子模块无缝测试。

在第二种情况中,它提到私有项“可以被“当前模块及其后代访问,但访问项的确切含义取决于项是什么。

访问模块意味着查看其内部(以导入更多项)。另一方面,访问函数意味着调用它。此外,路径表达式和导入语句被认为是在访问项,因为导入/表达式仅在目标在当前可见性作用域内时有效。

以下是一个示例程序,说明了上面概述的三种情况:

// This module is private, meaning that no external crate can access this
// module. Because it is private at the root of this current crate, however, any
// module in the crate may access any publicly visible item in this module.
mod crate_helper_module {

    // This function can be used by anything in the current crate
    pub fn crate_helper() {}

    // This function *cannot* be used by anything else in the crate. It is not
    // publicly visible outside of the `crate_helper_module`, so only this
    // current module and its descendants may access it.
    fn implementation_detail() {}
}

// This function is "public to the root" meaning that it's available to external
// crates linking against this one.
pub fn public_api() {}

// Similarly to 'public_api', this module is public so external crates may look
// inside of it.
pub mod submodule {
    use crate::crate_helper_module;

    pub fn my_method() {
        // Any item in the local crate may invoke the helper module's public
        // interface through a combination of the two rules above.
        crate_helper_module::crate_helper();
    }

    // This function is hidden to any module which is not a descendant of
    // `submodule`
    fn my_implementation() {}

    #[cfg(test)]
    mod test {

        #[test]
        fn test_my_implementation() {
            // Because this module is a descendant of `submodule`, it's allowed
            // to access private items inside of `submodule` without a privacy
            // violation.
            super::my_implementation();
        }
    }
}

fn main() {}

对于 Rust 程序通过隐私检查,所有路径必须是根据上述两条规则的有效访问。这包括所有 use 语句、表达式、类型等。

pub(in path)pub(crate)pub(super)pub(self)

除了公开和私有之外,Rust 允许用户声明项仅在给定作用域内可见。pub 限制的规则如下:

  • pub(in path) 使项在提供的 path 内可见。path 必须是简单路径,解析为正在声明可见性的项的祖先模块。path 中的每个标识符必须直接引用模块(不由 use 语句引入的名称)。
  • pub(crate) 使项在当前 crate 内可见。
  • pub(super) 使项对父模块可见。这等同于 pub(in super)
  • pub(self) 使项对当前模块可见。这等同于 pub(in self) 或完全不使用 pub

2018 Edition differences

从 2018 版本开始,pub(in path) 的路径必须以 crateselfsuper 开头。2015 版本也可以使用以 :: 开头的路径或来自 crate 根的模块。

这是一个示例:

pub mod outer_mod {
    pub mod inner_mod {
        // This function is visible within `outer_mod`
        pub(in crate::outer_mod) fn outer_mod_visible_fn() {}
        // Same as above, this is only valid in the 2015 edition.
        pub(in outer_mod) fn outer_mod_visible_fn_2015() {}

        // This function is visible to the entire crate
        pub(crate) fn crate_visible_fn() {}

        // This function is visible within `outer_mod`
        pub(super) fn super_mod_visible_fn() {
            // This function is visible since we're in the same `mod`
            inner_mod_visible_fn();
        }

        // This function is visible only within `inner_mod`,
        // which is the same as leaving it private.
        pub(self) fn inner_mod_visible_fn() {}
    }
    pub fn foo() {
        inner_mod::outer_mod_visible_fn();
        inner_mod::crate_visible_fn();
        inner_mod::super_mod_visible_fn();

        // This function is no longer visible since we're outside of `inner_mod`
        // Error! `inner_mod_visible_fn` is private
        //inner_mod::inner_mod_visible_fn();
    }
}

fn bar() {
    // This function is still visible since we're in the same crate
    outer_mod::inner_mod::crate_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `super_mod_visible_fn` is private
    //outer_mod::inner_mod::super_mod_visible_fn();

    // This function is no longer visible since we're outside of `outer_mod`
    // Error! `outer_mod_visible_fn` is private
    //outer_mod::inner_mod::outer_mod_visible_fn();

    outer_mod::foo();
}

fn main() { bar() }

Note

此语法仅向项的可见性添加另一个限制。它不保证该项在指定作用域的所有部分都可见。要访问一个项,其所有父项直到当前作用域都必须仍然可见。

重导出和可见性

Rust 允许通过 pub use 指令公开重导出项。因为这是一个公开指令,这允许根据上述规则在当前模块中使用该项。它本质上允许对重导出项的公开访问。例如,此程序是有效的:

pub use self::implementation::api;

mod implementation {
    pub mod api {
        pub fn f() {}
    }
}

fn main() {}

这意味着任何引用 implementation::api::f 的外部 crate 都会收到隐私违规,而路径 api::f 将被允许。

当重导出私有项时,可以认为它允许“隐私链“通过重导出短路,而不是像通常那样通过命名空间层次结构传递。

内存模型

Warning

Rust 的内存模型不完整且尚未完全确定。

字节

Rust 中最基本的内存单位是字节。

Note

虽然字节通常被降级为硬件字节,但 Rust 使用“抽象“的字节概念,可以区分硬件中不存在的差异,例如未初始化或存储指针的一部分。这些差异会影响您的程序是否具有未定义行为,因此它们仍然对编译后的 Rust 程序的行为产生实际影响。

每个字节可以具有以下值之一:

  • 包含 u8 值和可选[来源]provenance的已初始化字节,
  • 未初始化的字节。

Note

上述列表尚未保证是详尽的。

内存分配和生命周期

程序的_项_是那些函数、模块和类型,它们的值在编译时计算并唯一地存储在 rust 进程的内存映像中。项既不是动态分配的,也不是释放的。

_堆_是描述盒子的通用术语。堆中分配的生命周期取决于指向它的盒子值的生命周期。由于盒子值本身可以进出帧或存储在堆中,堆分配可能比分配它的帧存活更久。堆中的分配保证在分配的整个生命周期内驻留在堆中的单个位置 - 它永远不会因为移动盒子值而被重新定位。

变量

_变量_是栈帧的组成部分,可以是命名的函数参数、匿名的[临时值]temporary或命名的局部变量。

局部变量(或栈局部分配)直接持有值,分配在栈的内存中。值是栈帧的一部分。

局部变量除非另外声明,否则是不可变的。例如:let mut x = ...

函数参数除非用 mut 声明,否则是不可变的。mut 关键字仅适用于后面的参数。例如:|mut x, y|fn f(mut x: Box<i32>, y: Box<i32>) 声明一个可变变量 x 和一个不可变变量 y

局部变量在分配时未初始化。相反,整个帧的局部变量在帧进入时以未初始化状态分配。函数中的后续语句可能会也可能不会初始化局部变量。局部变量只能在通过所有可达控制流路径初始化后才能使用。

在下一个示例中,init_after_if 在 [if 表达式]之后初始化,而 uninit_after_if 未初始化,因为它在 else 情况下未初始化。

#![allow(unused)]
fn main() {
fn random_bool() -> bool { true }
fn initialization_example() {
    let init_after_if: ();
    let uninit_after_if: ();

    if random_bool() {
        init_after_if = ();
        uninit_after_if = ();
    } else {
        init_after_if = ();
    }

    init_after_if; // ok
    // uninit_after_if; // err: use of possibly uninitialized `uninit_after_if`
}
}

Panic

Rust 提供了一种机制来阻止函数正常返回,而是“panic“,这是对通常不期望在遇到错误的上下文中可恢复的错误条件的响应。

某些语言构造(如越界数组索引)会自动 panic。

还有一些语言功能提供对 panic 行为的控制级别:

Note

标准库提供了通过 panic!显式 panic 的能力。

panic_handler 属性

panic_handler 属性可应用于函数以定义 panic 的行为。

panic_handler 属性只能应用于签名 fn(&PanicInfo) -> ! 的函数。

Note

PanicInfo 结构体包含有关 panic 位置的信息。

依赖图中必须有一个单一的 panic_handler 函数。

下面显示了一个记录 panic 消息然后停止线程的 panic_handler 函数。

#![no_std]

use core::fmt::{self, Write};
use core::panic::PanicInfo;

struct Sink {
    // ..
   _0: (),
}

impl Sink {
    fn new() -> Sink { Sink { _0: () }}
}

impl fmt::Write for Sink {
    fn write_str(&mut self, _: &str) -> fmt::Result { Ok(()) }
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    let mut sink = Sink::new();

    // logs "panicked at '$reason', src/main.rs:27:4" to some `sink`
    let _ = writeln!(sink, "{}", info);

    loop {}
}

标准行为

std 提供两种不同的 panic 处理器:

  • unwind — 展开栈并可能可恢复。
  • abort –– 中止进程且不可恢复。

并非所有目标都可能提供 unwind 处理器。

Note

链接 std 时使用的 panic 处理器可以通过 -C panic CLI 标志设置。大多数目标的默认值是 unwind

标准库的 panic 行为可以在运行时通过 std::panic::set_hook 函数修改。

链接 no_std 二进制文件、dylib、cdylib 或 staticlib 将需要指定您自己的 panic 处理器。

Panic 策略

_Panic 策略_定义了 crate 构建时支持的 panic 行为类型。

Note

可以在 rustc 中使用 -C panic CLI 标志选择 panic 策略。

当生成二进制文件、dylib、cdylib 或 staticlib 并链接 std 时,-C panic CLI 标志还影响使用哪个panic 处理器

Note

使用 abort panic 策略编译代码时,优化器可以假设跨 Rust 帧展开是不可能的,这可能导致代码大小和运行时速度的改进。

Note

有关链接具有不同 panic 策略的 crate 的限制,请参阅 link.unwinding。这意味着使用 unwind 策略构建的 crate 可以使用 abort panic 处理器,但 abort 策略不能使用 unwind panic 处理器。

展开

Panic 可能是可恢复的或不可恢复的,尽管可以配置(通过选择非展开 panic 处理器)使其始终不可恢复。(反过来不成立:unwind 处理器不保证所有 panic 都是可恢复的,只保证通过 panic! 宏和类似标准库机制的 panic 是可恢复的。)

当 panic 发生时,unwind 处理器“展开“ Rust 帧,就像 C++ 的 throw 展开 C++ 帧一样,直到 panic 到达恢复点(例如在线程边界处)。这意味着当 panic 遍历 Rust 帧时,这些帧中实现 Drop 的活动对象将调用其 drop 方法。因此,当恢复正常执行时,不再可访问的对象将被“清理“,就像它们正常超出作用域一样。

Note

只要此资源清理的保证得到保留,“展开“可以在不实际使用 C++ 用于目标平台的机制的情况下实现。

Note

标准库提供了两种从 panic 恢复的机制,std::panic::catch_unwind(允许在 panic 线程内恢复)和 std::thread::spawn(自动为生成的线程设置 panic 恢复,以便其他线程可以继续运行)。

跨 FFI 边界的展开

可以使用适当的 ABI 声明跨 FFI 边界展开。虽然在某些情况下有用,但这为未定义行为创造了独特的机会,特别是当涉及多个语言运行时时。

使用错误的 ABI 展开是未定义行为:

  • 从通过使用非展开 ABI(如 "C""system" 等)声明的函数声明或指针调用的外部函数导致展开进入 Rust 代码。(例如,当 C++ 中编写的此类函数抛出未捕获的异常并传播到 Rust 时,会发生这种情况。)
  • 从不支持展开的代码(如使用 -fno-exceptions 的 GCC 或 Clang 编译的代码)调用展开的 Rust extern 函数(使用 extern "C-unwind" 或允许展开的其他 ABI)

使用 std::panic::catch_unwindstd::thread::JoinHandle::join 或让其传播到 Rust main() 函数或线程根之外来捕获外部展开操作(如 C++ 异常)将具有以下两种行为之一,并且未指定会发生哪种:

  • 进程中止。
  • 函数返回包含不透明类型的 Result::Err

Note

使用不同 Rust 标准库实例编译或链接的 Rust 代码在此保证中被视为“外部异常“。因此,使用 panic! 并链接到一个版本的 Rust 标准库的库,从使用不同版本标准库的应用程序调用,即使该库仅在子线程内使用,也可能导致整个应用程序中止。

目前没有关于外部运行时尝试处置或重新抛出 Rust panic 负载时发生的行为的保证。换句话说,源自 Rust 运行时的展开必须导致进程终止或被同一运行时捕获。

链接

Note

本节更多地从编译器的角度而非语言的角度进行描述。

编译器支持各种方法来静态和动态链接 crate。本节将探讨链接 crate 的各种方法,有关本机库的更多信息可以在书籍的 FFI 部分中找到。

在一次编译会话中,编译器可以通过使用命令行标志或 crate_type 属性生成多个工件。如果指定了一个或多个命令行标志,所有 crate_type 属性将被忽略,只构建命令行指定的工件。

  • --crate-type=bin#![crate_type = "bin"] - 将生成可运行的可执行文件。这要求 crate 中有一个 main 函数,在程序开始执行时运行。这将链接所有 Rust 和本机依赖项,生成单个可分发的二进制文件。这是默认的 crate 类型。
  • --crate-type=lib#![crate_type = "lib"] - 将生成 Rust 库。这是一个模糊的概念,因为库可以以多种形式体现。此通用 lib 选项的目的是生成“编译器推荐“风格的库。输出库将始终可被 rustc 使用,但实际的库类型可能会不时变化。其余输出类型都是库的不同变体,lib 类型可以看作是其中之一的别名(但实际的是编译器定义的)。
  • --crate-type=dylib#![crate_type = "dylib"] - 将生成动态 Rust 库。这与 lib 输出类型不同,因为它强制生成动态库。生成的动态库可用作其他库和/或可执行文件的依赖项。此输出类型将在 Linux 上创建 *.so 文件,在 macOS 上创建 *.dylib 文件,在 Windows 上创建 *.dll 文件。
  • --crate-type=staticlib#![crate_type = "staticlib"] - 将生成静态系统库。这与其他库输出不同,因为编译器永远不会尝试链接到 staticlib 输出。此输出类型的目的是创建包含所有本地 crate 代码以及所有上游依赖项的静态库。此输出类型将在 Linux、macOS 和 Windows (MinGW) 上创建 *.a 文件,在 Windows (MSVC) 上创建 *.lib 文件。此格式建议用于将 Rust 代码链接到现有非 Rust 应用程序等场景,因为它不会对其他 Rust 代码有动态依赖关系。

    请注意,静态库可能具有的任何动态依赖项(例如对系统库的依赖,或对编译为动态库的 Rust 库的依赖)在从某处链接该静态库时必须手动指定。--print=native-static-libs 标志可能对此有所帮助。

    请注意,由于生成的静态库包含所有依赖项的代码(包括标准库),并且还导出它们的所有公共符号,将静态库链接到可执行文件或共享库可能需要特别注意。对于共享库,导出符号列表必须通过链接器或符号版本脚本、导出符号列表 (macOS) 或模块定义文件 (Windows) 进行限制。此外,可以删除未使用的部分以删除所有未实际使用的依赖项代码(例如 --gc-sections 或 macOS 的 -dead_strip)。

  • --crate-type=cdylib#![crate_type = "cdylib"] - 将生成动态系统库。这用于编译要从另一种语言加载的动态库。此输出类型将在 Linux 上创建 *.so 文件,在 macOS 上创建 *.dylib 文件,在 Windows 上创建 *.dll 文件。
  • --crate-type=rlib#![crate_type = "rlib"] - 将生成“Rust 库“文件。这用作中间工件,可以看作是“静态 Rust 库“。这些 rlib 文件与 staticlib 文件不同,编译器在未来的链接中会解释它们。这本质上意味着 rustc 将在 rlib 文件中查找元数据,就像在动态库中查找元数据一样。此输出形式用于生成静态链接的可执行文件以及 staticlib 输出。
  • --crate-type=proc-macro#![crate_type = "proc-macro"] - 生成的输出未指定,但如果为其提供了 -L 路径,则编译器将识别输出工件为宏,并且可以为程序加载。使用此 crate 类型编译的 crate 必须只导出过程宏。编译器将自动设置 proc_macro配置选项。crate 始终使用编译器本身构建时的相同目标编译。例如,如果您从具有 x86_64 CPU 的 Linux 执行编译器,即使该 crate 是为不同目标构建的另一个 crate 的依赖项,目标也将是 x86_64-unknown-linux-gnu

请注意,这些输出是可堆叠的,如果指定了多个,则编译器将生成每种形式的输出而无需重新编译。但是,这仅适用于由同一方法指定的输出。如果只指定了 crate_type 属性,则它们都将被构建,但如果指定了一个或多个 --crate-type 命令行标志,则只构建这些输出。

对于所有这些不同类型的输出,如果 crate A 依赖于 crate B,则编译器可以在系统中找到各种不同形式的 B。然而,编译器查找的唯一格式是 rlib 格式和动态库格式。有了依赖库的这两个选项,编译器必须在某个时候在这两种格式之间做出选择。考虑到这一点,编译器在确定使用什么格式的依赖项时遵循以下规则:

  1. 如果正在生成静态库,则所有上游依赖项都必须以 rlib 格式提供。此要求源于动态库无法转换为静态格式的原因。

    请注意,无法将本机动态依赖项链接到静态库,在这种情况下,将打印有关所有未链接的本机动态依赖项的警告。

  1. 如果正在生成 rlib 文件,则对上游依赖项可用的格式没有限制。只需所有上游依赖项可用于读取元数据即可。

    原因是 rlib 文件不包含任何上游依赖项。所有 rlib 文件都包含 libstd.rlib 的副本效率不高!

  1. 如果正在生成可执行文件且未指定 -C prefer-dynamic 标志,则首先尝试以 rlib 格式查找依赖项。如果某些依赖项不可用 rlib 格式,则尝试动态链接(见下文)。
  1. 如果正在生成动态库或正在动态链接的可执行文件,则编译器将尝试协调 rlib 或 dylib 格式的可用依赖项以创建最终产品。

    编译器的一个主要目标是确保库在任何工件中不会出现多次。例如,如果动态库 B 和 C 都静态链接到库 A,则 crate 不能同时链接到 B 和 C,因为会有 A 的两个副本。编译器允许混合 rlib 和 dylib 格式,但必须满足此限制。

    编译器目前没有实现提示库应以何种格式链接的方法。在动态链接时,编译器将尝试最大化动态依赖项,同时仍允许某些依赖项通过 rlib 链接。

    对于大多数情况,如果动态链接,建议所有库都以 dylib 形式提供。对于其他情况,如果编译器无法确定每个库应链接的格式,将发出警告。

通常,--crate-type=bin--crate-type=lib 应该足以满足所有编译需求,如果需要对 crate 的输出格式进行更细粒度的控制,可以使用其他选项。

静态和动态 C 运行时

标准库通常力求支持目标的静态链接和动态链接 C 运行时。例如,x86_64-pc-windows-msvcx86_64-unknown-linux-musl 目标通常同时提供两种运行时,用户可以选择他们想要的。编译器中的所有目标都有链接到 C 运行时的默认模式。通常目标默认动态链接,但也有例外,默认为静态链接,例如:

  • arm-unknown-linux-musleabi
  • arm-unknown-linux-musleabihf
  • armv7-unknown-linux-musleabihf
  • i686-unknown-linux-musl
  • x86_64-unknown-linux-musl

C 运行时的链接配置为遵循 crt-static 目标功能。这些目标功能通常通过编译器本身的命令行标志进行配置。例如,要启用静态运行时,您将执行:

rustc -C target-feature=+crt-static foo.rs

而要动态链接到 C 运行时,您将执行:

rustc -C target-feature=-crt-static foo.rs

不支持在 C 运行时链接之间切换的目标将忽略此标志。建议在编译器成功后检查生成的二进制文件以确保其按预期链接。

crate 也可以了解 C 运行时的链接方式。例如,MSVC 上的代码需要根据链接的运行时进行不同的编译(例如使用 /MT/MD)。这目前通过 [cfg 属性 target_feature 选项]导出:

#![allow(unused)]
fn main() {
#[cfg(target_feature = "crt-static")]
fn foo() {
    println!("the C runtime should be statically linked");
}

#[cfg(not(target_feature = "crt-static"))]
fn foo() {
    println!("the C runtime should be dynamically linked");
}
}

另请注意,Cargo 构建脚本可以通过环境变量了解此功能。在构建脚本中,您可以通过以下方式检测链接:

use std::env;

fn main() {
    let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new());

    if linkage.contains("crt-static") {
        println!("the C runtime will be statically linked");
    } else {
        println!("the C runtime will be dynamically linked");
    }
}

要在本地使用此功能,您通常会使用 RUSTFLAGS 环境变量通过 Cargo 指定编译器标志。例如,要在 MSVC 上编译静态链接的二进制文件,您将执行:

RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-pc-windows-msvc

混合 Rust 和外部代码库

如果您将 Rust 与外部代码(例如 C、C++)混合使用,并希望创建包含两种代码类型的单个二进制文件,您有两种方法进行最终二进制链接:

  • 使用 rustc。使用 -L <directory>-l<library> rustc 参数传递任何非 Rust 库,和/或在 Rust 代码中使用 #[link] 指令。如果需要链接 .o 文件,可以使用 -Clink-arg=file.o
  • 使用您的外部链接器。在这种情况下,您首先需要生成 Rust staticlib 目标并将其传递到外部链接器调用中。如果需要链接多个 Rust 子系统,您将需要生成_单个_ staticlib,可能使用大量 extern crate 语句来包含多个 Rust rlib。多个 Rust staticlib 文件可能会冲突。

目前不支持将 rlib 直接传递到外部链接器。

Note

在本节中,使用不同 Rust 运行时实例编译或链接的 Rust 代码被视为“外部代码“。

禁止的链接和展开

只有在二进制文件根据以下规则一致构建时,才能使用 Panic 展开。

如果满足以下任一条件,则 Rust 工件称为可能展开

  • 工件使用 unwind panic 处理器
  • 工件包含使用 unwind panic 策略构建的 crate,该 crate 调用使用 -unwind ABI 的函数。
  • 工件对在另一个具有单独 Rust 运行时副本的 Rust 工件中运行的代码进行 "Rust" ABI 调用,并且该另一个工件可能展开。

Note

此定义捕获了 Rust 工件内的 "Rust" ABI 调用是否曾经展开。

如果 Rust 工件可能展开,则其所有 crate 必须使用 unwind panic 策略构建。否则,展开可能导致未定义行为。

Note

如果您使用 rustc 进行链接,这些规则会自动强制执行。如果您使用 rustc 进行链接,您必须注意确保展开在整个二进制文件中得到一致处理。不使用 rustc 的链接包括使用 dlopen 或类似设施,其中链接由系统运行时完成而不涉及 rustc。这只能在使用不同 -C panic 标志的代码混合时发生,因此大多数用户不必担心这一点。

Note

为保证库在链接时使用的 panic 运行时无论如何都是健全的(并且可与 rustc 链接),可以使用 ffi_unwind_calls lint。该 lint 标记任何对 -unwind 外部函数或函数指针的调用。

内联汇编

内联汇编的支持通过 asm!naked_asm!global_asm! 宏提供。它可用于将手写汇编嵌入到编译器生成的汇编输出中。

内联汇编在以下架构上是稳定的:

  • x86 和 x86-64
  • ARM
  • AArch64 和 Arm64EC
  • RISC-V
  • LoongArch
  • s390x
  • PowerPC 和 PowerPC64

如果在不支持的目标上使用汇编宏,编译器将发出错误。

示例

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
use std::arch::asm;

// Multiply x by 6 using shifts and adds
let mut x: u64 = 4;
unsafe {
    asm!(
        "mov {tmp}, {x}",
        "shl {tmp}, 1",
        "shl {x}, 2",
        "add {x}, {tmp}",
        x = inout(reg) x,
        tmp = out(reg) _,
    );
}
assert_eq!(x, 4 * 6);
}
}

语法

以下语法指定可以传递给 asm!global_asm!naked_asm! 宏的参数。

Syntax
AsmArgsAsmAttrFormatString ( , AsmAttrFormatString )* ( , AsmAttrOperand )* ,?

FormatStringSTRING_LITERAL | RAW_STRING_LITERAL | MacroInvocation

AsmAttrFormatString → ( OuterAttribute )* FormatString

AsmOperand
      ClobberAbi
    | AsmOptions
    | RegOperand

AsmAttrOperand → ( OuterAttribute )* AsmOperand

ClobberAbiclobber_abi ( Abi ( , Abi )* ,? )

AsmOptions
    options ( ( AsmOption ( , AsmOption )* ,? )? )

AsmOption
      pure
    | nomem
    | readonly
    | preserves_flags
    | noreturn
    | nostack
    | att_syntax
    | raw

RegOperand → ( ParamName = )?
    (
          DirSpec ( RegSpec ) Expression
        | DualDirSpec ( RegSpec ) DualDirSpecExpression
        | sym PathExpression
        | const Expression
        | label { Statements? }
    )

ParamNameIDENTIFIER_OR_KEYWORD | RAW_IDENTIFIER

DualDirSpecExpression
      Expression
    | Expression => Expression

RegSpecRegisterClass | ExplicitRegister

RegisterClassIDENTIFIER_OR_KEYWORD

ExplicitRegisterSTRING_LITERAL

DirSpec
      in
    | out
    | lateout

DualDirSpec
      inout
    | inlateout

作用域

内联汇编可以通过三种方式之一使用。

使用 asm! 宏,汇编代码在函数作用域中发出,并集成到编译器生成的函数汇编代码中。此汇编代码必须遵守严格规则以避免未定义行为。请注意,在某些情况下,编译器可能选择将汇编代码作为单独的函数发出并生成对它的调用。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
unsafe { core::arch::asm!("/* {} */", in(reg) 0); }
}
}

使用 naked_asm! 宏,汇编代码在函数作用域中发出,并构成函数的完整汇编代码。naked_asm! 宏仅在[裸函数]naked functions中允许。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
#[unsafe(naked)]
extern "C" fn wrapper() {
core::arch::naked_asm!("/* {} */", const 0);
}
}
}

使用 global_asm! 宏,汇编代码在全局作用域中发出,在函数外部。这可用于使用汇编代码手写整个函数,并且通常提供更大的自由度来使用任意寄存器和汇编器指令。

fn main() {}
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("/* {} */", const 0);

模板字符串参数

汇编器模板使用与[格式字符串]format strings相同的语法(即占位符由花括号指定)。

相应的参数按顺序、按索引或按名称访问。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
let y: i64;
let z: i64;
// This
unsafe { core::arch::asm!("mov {}, {}", out(reg) x, in(reg) 5); }
// ... this
unsafe { core::arch::asm!("mov {0}, {1}", out(reg) y, in(reg) 5); }
// ... and this
unsafe { core::arch::asm!("mov {out}, {in}", out = out(reg) z, in = in(reg) 5); }
// all have the same behavior
assert_eq!(x, y);
assert_eq!(y, z);
}
}

然而,不支持隐式命名参数(由 RFC #2795 引入)。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x = 5;
// We can't refer to `x` from the scope directly, we need an operand like `in(reg) x`
unsafe { core::arch::asm!("/* {x} */"); } // ERROR: no argument named x
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

asm! 调用可以有一个或多个模板字符串参数;具有多个模板字符串参数的 asm! 被视为所有字符串之间用 \n 连接。预期用途是每个模板字符串参数对应一行汇编代码。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
let y: i64;
// We can separate multiple strings as if they were written together
unsafe { core::arch::asm!("mov eax, 5", "mov ecx, eax", out("rax") x, out("rcx") y); }
assert_eq!(x, y);
}
}

所有模板字符串参数必须出现在任何其他参数之前。

#![allow(unused)]
fn main() {
let x = 5;
#[cfg(target_arch = "x86_64")] {
// The template strings need to appear first in the asm invocation
unsafe { core::arch::asm!("/* {x} */", x = const 5, "ud2"); } // ERROR: unexpected token
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

与格式字符串一样,位置参数必须出现在命名参数和显式寄存器操作数之前。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// Named operands need to come after positional ones
unsafe { core::arch::asm!("/* {x} {} */", x = const 5, in(reg) 5); }
// ERROR: positional arguments cannot follow named arguments or explicit register arguments
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// We also can't put explicit registers before positional operands
unsafe { core::arch::asm!("/* {} */", in("eax") 0, in(reg) 5); }
// ERROR: positional arguments cannot follow named arguments or explicit register arguments
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

显式寄存器操作数不能被模板字符串中的占位符使用。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// Explicit register operands don't get substituted, use `eax` explicitly in the string
unsafe { core::arch::asm!("/* {} */", in("eax") 5); }
// ERROR: invalid reference to argument at index 0
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

所有其他命名和位置操作数必须在模板字符串中至少出现一次,否则会产生编译器错误。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// We have to name all of the operands in the format string
unsafe { core::arch::asm!("", in(reg) 5, x = const 5); }
// ERROR: multiple unused asm arguments
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

确切的汇编代码语法是特定于目标的,对编译器来说是不透明的,除了操作数被替换到模板字符串中以形成传递给汇编器的代码的方式。

目前,所有支持的目标都遵循 LLVM 内部汇编器使用的汇编代码语法,该语法通常对应于 GNU 汇编器 (GAS) 的语法。在 x86 上,默认使用 GAS 的 .intel_syntax noprefix 模式。在 ARM 上,使用 .syntax unified 模式。这些目标对汇编代码施加了额外的限制:任何汇编器状态(例如可以用 .section 更改的当前部分)必须在 asm 字符串末尾恢复到其原始值。不符合 GAS 语法的汇编代码将导致特定于汇编器的行为。内联汇编使用的指令的进一步约束由指令支持指示。

属性

在语义上,只有 cfgcfg_attr 属性在内联汇编模板字符串和操作数上被接受。其他属性被解析但在汇编宏展开时被拒绝。

fn main() {}
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!(
    #[cfg(not(panic = "abort"))]
    ".cfi_startproc",
    // ...
    "ret",
    #[cfg(not(panic = "abort"))]
    ".cfi_endproc",
);

Note

rustc 中,汇编宏对这些属性的处理与处理语言中类似属性的正常系统分开。这解释了支持的属性种类有限,并可能导致行为上的细微差异。

在语法上,第一个操作数之前必须至少有一个模板字符串。

#![allow(unused)]
fn main() {
// This is rejected because `a = out(reg) x` does not parse as a
// template string.
core::arch::asm!(
    #[cfg(false)]
    a = out(reg) x, // ERROR.
    "",
);
}

操作数类型

支持几种类型的操作数:

  • in(<reg>) <expr>
    • <reg> 可以引用寄存器类或显式寄存器。分配的寄存器名称被替换到 asm 模板字符串中。
    • 分配的寄存器在汇编代码开头将包含 <expr> 的值。
    • 分配的寄存器在汇编代码末尾必须包含相同的值(除非 lateout 分配到同一寄存器)。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// ``in` can be used to pass values into inline assembly...
unsafe { core::arch::asm!("/* {} */", in(reg) 5); }
}
}

Note

如果值的类型小于寄存器,则高位的值是特定于平台的。某些目标将高位清零,而其他目标则保持不变。

  • out(<reg>) <expr>
    • <reg> 可以引用寄存器类或显式寄存器。分配的寄存器名称被替换到 asm 模板字符串中。
    • 分配的寄存器在汇编代码开头将包含未定义的值。
    • <expr> 必须是(可能未初始化的)位置表达式,分配的寄存器的内容在汇编代码末尾写入该位置。
    • 可以指定下划线(_)代替表达式,这将导致寄存器的内容在汇编代码末尾被丢弃(实际上充当 clobber)。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
// and `out` can be used to pass values back to rust.
unsafe { core::arch::asm!("/* {} */", out(reg) x); }
}
}
  • lateout(<reg>) <expr>
    • out 相同,只是寄存器分配器可以重用分配给 in 的寄存器。
    • 您应该只在所有输入都被读取后才写入寄存器,否则可能会破坏输入。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
// `lateout` is the same as `out`
// but the compiler knows we don't care about the value of any inputs by the
// time we overwrite it.
unsafe { core::arch::asm!("mov {}, 5", lateout(reg) x); }
assert_eq!(x, 5)
}
}
  • inout(<reg>) <expr>
    • <reg> 可以引用寄存器类或显式寄存器。分配的寄存器名称被替换到 asm 模板字符串中。
    • 分配的寄存器在汇编代码开头将包含 <expr> 的值。
    • <expr> 必须是可变的已初始化位置表达式,分配的寄存器的内容在汇编代码末尾写入该位置。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x: i64 = 4;
// `inout` can be used to modify values in-register
unsafe { core::arch::asm!("inc {}", inout(reg) x); }
assert_eq!(x, 5);
}
}
  • inout(<reg>) <in expr> => <out expr>
    • inout 相同,只是寄存器的初始值取自 <in expr> 的值。
    • <out expr> 必须是(可能未初始化的)位置表达式,分配的寄存器的内容在汇编代码末尾写入该位置。
    • 可以为 <out expr> 指定下划线(_)代替表达式,这将导致寄存器的内容在汇编代码末尾被丢弃(实际上充当 clobber)。
    • <in expr><out expr> 可以有不同的类型。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
// `inout` can also move values to different places
unsafe { core::arch::asm!("inc {}", inout(reg) 4u64=>x); }
assert_eq!(x, 5);
}
}
  • inlateout(<reg>) <expr> / inlateout(<reg>) <in expr> => <out expr>
    • inout 相同,只是寄存器分配器可以重用分配给 in 的寄存器(如果编译器知道 ininlateout 具有相同的初始值,则可能发生这种情况)。
    • 您应该只在所有输入都被读取后才写入寄存器,否则可能会破坏输入。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x: i64 = 4;
// `inlateout` is `inout` using `lateout`
unsafe { core::arch::asm!("inc {}", inlateout(reg) x); }
assert_eq!(x, 5);
}
}
  • sym <path>
    • <path> 必须引用 fnstatic
    • 引用该项的修饰符号名称被替换到 asm 模板字符串中。
    • 替换的字符串不包含任何修饰符(例如 GOT、PLT、重定位等)。
    • 允许 <path> 指向 #[thread_local] 静态,在这种情况下,汇编代码可以将符号与重定位(例如 @plt@TPOFF)组合以从线程本地数据读取。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo() {
    println!("Hello from inline assembly")
}
// `sym` can be used to refer to a function (even if it doesn't have an
// external name we can directly write)
unsafe { core::arch::asm!("call {}", sym foo, clobber_abi("C")); }
}
}
  • const <expr>
    • <expr> 必须是整数常量表达式。此表达式遵循与内联 const 块相同的规则。
    • 表达式的类型可以是任何整数类型,但默认为 i32,就像整数字面量一样。
    • 表达式的值被格式化为字符串并直接替换到 asm 模板字符串中。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// swizzle [0, 1, 2, 3] => [3, 2, 0, 1]
const SHUFFLE: u8 = 0b01_00_10_11;
let x: core::arch::x86_64::__m128 = unsafe { core::mem::transmute([0u32, 1u32, 2u32, 3u32]) };
let y: core::arch::x86_64::__m128;
// Pass a constant value into an instruction that expects an immediate like `pshufd`
unsafe {
    core::arch::asm!("pshufd {xmm}, {xmm}, {shuffle}",
        xmm = inlateout(xmm_reg) x=>y,
        shuffle = const SHUFFLE
    );
}
let y: [u32; 4] = unsafe { core::mem::transmute(y) };
assert_eq!(y, [3, 2, 0, 1]);
}
}
  • label <block>
    • 块的地址被替换到 asm 模板字符串中。汇编代码可以跳转到替换的地址。
    • 对于区分直接跳转和间接跳转的目标(例如启用 cf-protection 的 x86-64),汇编代码不得间接跳转到替换的地址。
    • 执行块后,asm! 表达式返回。
    • 块的类型必须是单元或 !(永不)。
    • 块启动新的安全上下文;label 块内的不安全操作必须包装在内部 unsafe 块中,即使整个 asm! 表达式已经包装在 unsafe 中。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")]
unsafe {
    core::arch::asm!("jmp {}", label {
        println!("Hello from inline assembly label");
    });
}
}

操作数表达式从左到右求值,就像函数调用参数一样。asm! 执行后,输出按从左到右的顺序写入。如果两个输出指向同一位置,这很重要:该位置将包含最右侧输出的值。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut y: i64;
// y gets its value from the second output, rather than the first
unsafe { core::arch::asm!("mov {}, 0", "mov {}, 1", out(reg) y, out(reg) y); }
assert_eq!(y, 1);
}
}

由于 naked_asm! 定义了整个函数主体,编译器无法发出任何额外代码来处理操作数,因此它只能使用 symconst 操作数。

由于 global_asm! 存在于函数外部,因此它只能使用 symconst 操作数。

fn main() {}
// register operands aren't allowed, since we aren't in a function
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("", in(reg) 5);
// ERROR: the `in` operand cannot be used with `global_asm!`
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
fn main() {}
fn foo() {}

#[cfg(target_arch = "x86_64")]
// `const` and `sym` are both allowed, however
core::arch::global_asm!("/* {} {} */", const 0, sym foo);

寄存器操作数

输入和输出操作数可以指定为显式寄存器或寄存器类,寄存器分配器可以从中选择寄存器。显式寄存器指定为字符串字面量(例如 "eax"),而寄存器类指定为标识符(例如 reg)。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut y: i64;
// We can name both `reg`, or an explicit register like `eax` to get an
// integer register
unsafe { core::arch::asm!("mov eax, {:e}", in(reg) 5, lateout("eax") y); }
assert_eq!(y, 5);
}
}

请注意,显式寄存器将寄存器别名(例如 ARM 上的 r14lr)和寄存器的较小视图(例如 eaxrax)视为等同于基本寄存器。

对两个输入操作数或两个输出操作数使用相同的显式寄存器是编译时错误。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// We can't name eax twice
unsafe { core::arch::asm!("", in("eax") 5, in("eax") 4); }
// ERROR: register `eax` conflicts with register `eax`
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// ... even using different aliases
unsafe { core::arch::asm!("", in("ax") 5, in("rax") 4); }
// ERROR: register `rax` conflicts with register `ax`
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

此外,在输入操作数或输出操作数中使用重叠寄存器(例如 ARM VFP)也是编译时错误。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// al overlaps with ax, so we can't name both of them.
unsafe { core::arch::asm!("", in("ax") 5, in("al") 4i8); }
// ERROR: register `al` conflicts with register `ax`
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

只有以下类型允许作为内联汇编的操作数:

  • 整数(有符号和无符号)
  • 浮点数
  • 指针(仅限瘦指针)
  • 函数指针
  • SIMD 向量(使用 #[repr(simd)] 定义并实现 Copy 的结构体)。这包括在 std::arch 中定义的特定于架构的向量类型,如 __m128(x86)或 int8x16_t(ARM)。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo() {}

// Integers are allowed...
let y: i64 = 5;
unsafe { core::arch::asm!("/* {} */", in(reg) y); }

// and pointers...
let py = &raw const y;
unsafe { core::arch::asm!("/* {} */", in(reg) py); }

// floats as well...
let f = 1.0f32;
unsafe { core::arch::asm!("/* {} */", in(xmm_reg) f); }

// even function pointers and simd vectors.
let func: extern "C" fn() = foo;
unsafe { core::arch::asm!("/* {} */", in(reg) func); }

let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) };
unsafe { core::arch::asm!("/* {} */", in(xmm_reg) z); }
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
struct Foo;
let x: Foo = Foo;
// Complex types like structs are not allowed
unsafe { core::arch::asm!("/* {} */", in(reg) x); }
// ERROR: cannot use value of type `Foo` for inline assembly
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

以下是当前支持的寄存器类列表:

架构寄存器类寄存器LLVM 约束代码
x86regax, bx, cx, dx, si, di, bp, r[8-15](仅 x86-64)r
x86reg_abcdax, bx, cx, dxQ
x86-32reg_byteal, bl, cl, dl, ah, bh, ch, dhq
x86-64reg_byte*al, bl, cl, dl, sil, dil, bpl, r[8-15]bq
x86xmm_regxmm[0-7](x86)xmm[0-15](x86-64)x
x86ymm_regymm[0-7](x86)ymm[0-15](x86-64)x
x86zmm_regzmm[0-7](x86)zmm[0-31](x86-64)v
x86kregk[1-7]Yk
x86kreg0k0仅 clobber
x86x87_regst([0-7])仅 clobber
x86mmx_regmm[0-7]仅 clobber
x86-64tmm_regtmm[0-7]仅 clobber
AArch64regx[0-30]r
AArch64vregv[0-31]w
AArch64vreg_low16v[0-15]x
AArch64pregp[0-15], ffr仅 clobber
Arm64ECregx[0-12], x[15-22], x[25-27], x30r
Arm64ECvregv[0-15]w
Arm64ECvreg_low16v[0-15]x
ARM (ARM/Thumb2)regr[0-12], r14r
ARM (Thumb1)regr[0-7]r
ARMsregs[0-31]t
ARMsreg_low16s[0-15]x
ARMdregd[0-31]w
ARMdreg_low16d[0-15]t
ARMdreg_low8d[0-8]x
ARMqregq[0-15]w
ARMqreg_low8q[0-7]t
ARMqreg_low4q[0-3]x
RISC-Vregx1, x[5-7], x[9-15], x[16-31](非 RV32E)r
RISC-Vfregf[0-31]f
RISC-Vvregv[0-31]仅 clobber
LoongArchreg$r1, $r[4-20], $r[23,30]r
LoongArchfreg$f[0-31]f
s390xregr[0-10], r[12-14]r
s390xreg_addrr[1-10], r[12-14]a
s390xfregf[0-15]f
s390xvregv[0-31]v
s390xarega[2-15]仅 clobber
PowerPCregr0, r[3-12], r[14-28]r
PowerPCreg_nonzeror[3-12], r[14-28]b
PowerPCspe_accspe_acc仅 clobber
PowerPC64regr0, r[3-12], r[14-29]r
PowerPC64reg_nonzeror[3-12], r[14-29]b
PowerPC/PowerPC64fregf[0-31]f
PowerPC/PowerPC64vregv[0-31]v
PowerPC/PowerPC64vsregvs[0-63]wa
PowerPC/PowerPC64crcr[0-7], cr仅 clobber
PowerPC/PowerPC64ctrctr仅 clobber
PowerPC/PowerPC64lrlr仅 clobber
PowerPC/PowerPC64xerxer仅 clobber

Note

  • 在 x86 上,我们将 reg_bytereg 区别对待,因为编译器可以单独分配 alah,而 reg 保留整个寄存器。
  • 在 x86-64 上,高字节寄存器(例如 ah)在 reg_byte 寄存器类中不可用。
  • 某些寄存器类标记为“仅 clobber“,这意味着这些类中的寄存器不能用于输入或输出,只能用于 out(<explicit register>) _lateout(<explicit register>) _ 形式的 clobber。
  • spe_acc 寄存器仅在 PowerPC SPE 目标上可用。

每个寄存器类对其可以使用的值类型有限制。这是必要的,因为将值加载到寄存器的方式取决于其类型。例如,在大端系统上,将 i32x4i8x16 加载到 SIMD 寄存器中可能导致不同的寄存器内容,即使两个值的按字节内存表示是相同的。特定寄存器类支持的类型的可用性可能取决于当前启用的目标功能。

架构寄存器类目标功能允许的类型
x86-32regi16, i32, f32
x86-64regi16, i32, f32, i64, f64
x86reg_bytei8
x86xmm_regssei32, f32, i64, f64,
i8x16, i16x8, i32x4, i64x2, f32x4, f64x2
x86ymm_regavxi32, f32, i64, f64,
i8x16, i16x8, i32x4, i64x2, f32x4, f64x2
i8x32, i16x16, i32x8, i64x4, f32x8, f64x4
x86zmm_regavx512fi32, f32, i64, f64,
i8x16, i16x8, i32x4, i64x2, f32x4, f64x2
i8x32, i16x16, i32x8, i64x4, f32x8, f64x4
i8x64, i16x32, i32x16, i64x8, f32x16, f64x8
x86kregavx512fi8, i16
x86kregavx512bwi32, i64
x86mmx_regN/A仅 clobber
x86x87_regN/A仅 clobber
x86tmm_regN/A仅 clobber
AArch64regi8, i16, i32, f32, i64, f64
AArch64vregneoni8, i16, i32, f32, i64, f64,
i8x8, i16x4, i32x2, i64x1, f32x2, f64x1,
i8x16, i16x8, i32x4, i64x2, f32x4, f64x2
AArch64pregN/A仅 clobber
Arm64ECregi8, i16, i32, f32, i64, f64
Arm64ECvregneoni8, i16, i32, f32, i64, f64,
i8x8, i16x4, i32x2, i64x1, f32x2, f64x1,
i8x16, i16x8, i32x4, i64x2, f32x4, f64x2
ARMregi8, i16, i32, f32
ARMsregvfp2i32, f32
ARMdregvfp2i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2
ARMqregneoni8x16, i16x8, i32x4, i64x2, f32x4
RISC-V32regi8, i16, i32, f32
RISC-V64regi8, i16, i32, f32, i64, f64
RISC-Vfregff32
RISC-Vfregdf64
RISC-VvregN/A仅 clobber
LoongArch32regi8, i16, i32, f32
LoongArch64regi8, i16, i32, i64, f32, f64
LoongArchfregff32
LoongArchfregdf64
s390xreg, reg_addri8, i16, i32, i64
s390xfregf32, f64
s390xvregvectori32, f32, i64, f64, i128,
i8x16, i16x8, i32x4, i64x2, f32x4, f64x2
s390xaregN/A仅 clobber
PowerPCspe_acc仅 clobber
PowerPC/PowerPC64regi8, i16, i32, i64(仅 PowerPC64)
PowerPC/PowerPC64reg_nonzeroi8, i16, i32, i64(仅 PowerPC64)
PowerPC/PowerPC64fregf32, f64
PowerPC/PowerPC64vregaltiveci8x16, i16x8, i32x4, f32x4
PowerPC/PowerPC64vregvsxf32, f64, i64x2, f64x2
PowerPC/PowerPC64vsregvsxvsx 和 altivec vreg 类型的并集
PowerPC/PowerPC64cr仅 clobber
PowerPC/PowerPC64ctr仅 clobber
PowerPC/PowerPC64lr仅 clobber
PowerPC/PowerPC64xer仅 clobber

Note

对于上表,指针、函数指针和 isize/usize 被视为等效的整数类型(根据目标为 i16/i32/i64)。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x = 5i32;
let y = -1i8;
let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) };

// reg is valid for `i32`, `reg_byte` is valid for `i8`, and xmm_reg is valid for `__m128i`
// We can't use `tmm0` as an input or output, but we can clobber it.
unsafe { core::arch::asm!("/* {} {} {} */", in(reg) x, in(reg_byte) y, in(xmm_reg) z, out("tmm0") _); }
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) };
// We can't pass an `__m128i` to a `reg` input
unsafe { core::arch::asm!("/* {} */", in(reg) z); }
// ERROR: type `__m128i` cannot be used with this register class
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

如果值的大小小于其分配的寄存器,则该寄存器的高位对于输入将具有未定义的值,对于输出将被忽略。唯一的例外是 RISC-V 上的 freg 寄存器类,其中 f32 值按照 RISC-V 架构的要求在 f64 中进行 NaN 装箱。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x: i64;
// Moving a 32-bit value into a 64-bit value, oops.
#[allow(asm_sub_register)] // rustc warns about this behavior
unsafe { core::arch::asm!("mov {}, {}", lateout(reg) x, in(reg) 4i32); }
// top 32-bits are indeterminate
assert_eq!(x, 4); // This assertion is not guaranteed to succeed
assert_eq!(x & 0xFFFFFFFF, 4); // However, this one will succeed
}
}

当为 inout 操作数指定单独的输入和输出表达式时,两个表达式必须具有相同的类型。唯一的例外是两个操作数都是指针或整数,在这种情况下它们只需要具有相同的大小。此限制存在是因为 LLVM 和 GCC 中的寄存器分配器有时无法处理具有不同类型的绑定操作数。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// Pointers and integers can mix (as long as they are the same size)
let x: isize = 0;
let y: *mut ();
// Transmute an `isize` to a `*mut ()`, using inline assembly magic
unsafe { core::arch::asm!("/*{}*/", inout(reg) x=>y); }
assert!(y.is_null()); // Extremely roundabout way to make a null pointer
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let y: f32;
// But we can't reinterpret an `i32` to an `f32` like this
unsafe { core::arch::asm!("/* {} */", inout(reg) x=>y); }
// ERROR: incompatible types for asm inout argument
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

寄存器名称

某些寄存器有多个名称。这些都被编译器视为与基本寄存器名称相同。以下是所有支持的寄存器别名列表:

架构基本寄存器别名
x86axeax, rax
x86bxebx, rbx
x86cxecx, rcx
x86dxedx, rdx
x86siesi, rsi
x86diedi, rdi
x86bpbpl, ebp, rbp
x86spspl, esp, rsp
x86ipeip, rip
x86st(0)st
x86r[8-15]r[8-15]b, r[8-15]w, r[8-15]d
x86xmm[0-31]ymm[0-31], zmm[0-31]
AArch64x[0-30]w[0-30]
AArch64x29fp
AArch64x30lr
AArch64spwsp
AArch64xzrwzr
AArch64v[0-31]b[0-31], h[0-31], s[0-31], d[0-31], q[0-31]
Arm64ECx[0-30]w[0-30]
Arm64ECx29fp
Arm64ECx30lr
Arm64ECspwsp
Arm64ECxzrwzr
Arm64ECv[0-15]b[0-15], h[0-15], s[0-15], d[0-15], q[0-15]
ARMr[0-3]a[1-4]
ARMr[4-9]v[1-6]
ARMr9rfp
ARMr10sl
ARMr11fp
ARMr12ip
ARMr13sp
ARMr14lr
ARMr15pc
RISC-Vx0zero
RISC-Vx1ra
RISC-Vx2sp
RISC-Vx3gp
RISC-Vx4tp
RISC-Vx[5-7]t[0-2]
RISC-Vx8fp, s0
RISC-Vx9s1
RISC-Vx[10-17]a[0-7]
RISC-Vx[18-27]s[2-11]
RISC-Vx[28-31]t[3-6]
RISC-Vf[0-7]ft[0-7]
RISC-Vf[8-9]fs[0-1]
RISC-Vf[10-17]fa[0-7]
RISC-Vf[18-27]fs[2-11]
RISC-Vf[28-31]ft[8-11]
LoongArch$r0$zero
LoongArch$r1$ra
LoongArch$r2$tp
LoongArch$r3$sp
LoongArch$r[4-11]$a[0-7]
LoongArch$r[12-20]$t[0-8]
LoongArch$r21
LoongArch$r22$fp, $s9
LoongArch$r[23-31]$s[0-8]
LoongArch$f[0-7]$fa[0-7]
LoongArch$f[8-23]$ft[0-15]
LoongArch$f[24-31]$fs[0-7]
PowerPC/PowerPC64r1sp
PowerPC/PowerPC64r31fp
PowerPC/PowerPC64r[0-31][0-31]
PowerPC/PowerPC64f[0-31]fr[0-31]
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let z = 0i64;
// rax is an alias for eax and ax
unsafe { core::arch::asm!("", in("rax") z); }
}
}

某些寄存器不能用于输入或输出操作数:

架构不支持的寄存器原因
所有sp, r15(s390x), r1(PowerPC 和 PowerPC64)栈指针必须在汇编代码末尾或跳转到 label 块之前恢复到其原始值。
所有bp(x86), x29(AArch64 和 Arm64EC), x8(RISC-V), $fp(LoongArch), r11(s390x), fp(PowerPC 和 PowerPC64)帧指针不能用作输入或输出。
ARMr7r11在 ARM 上,帧指针可以是 r7r11,具体取决于目标。帧指针不能用作输入或输出。
所有si(x86-32), bx(x86-64), r6(ARM), x19(AArch64 和 Arm64EC), x9(RISC-V), $s8(LoongArch), r29r30(PowerPC), r30(PowerPC64)LLVM 在内部将其用作具有复杂栈帧的函数的“基指针“。
x86ip这是程序计数器,不是真正的寄存器。
AArch64xzr这是一个常量零寄存器,不能被修改。
AArch64x18在某些 AArch64 目标上,这是操作系统保留的寄存器。
Arm64ECxzr这是一个常量零寄存器,不能被修改。
Arm64ECx18这是操作系统保留的寄存器。
Arm64ECx13, x14, x23, x24, x28, v[16-31], p[0-15], ffr这些是 Arm64EC 不支持的 AArch64 寄存器。
ARMpc这是程序计数器,不是真正的寄存器。
ARMr9在某些 ARM 目标上,这是操作系统保留的寄存器。
RISC-Vx0这是一个常量零寄存器,不能被修改。
RISC-Vgp, tp这些寄存器是保留的,不能用作输入或输出。
LoongArch$r0$zero这是一个常量零寄存器,不能被修改。
LoongArch$r2$tp这是为 TLS 保留的。
LoongArch$r21这是 ABI 保留的。
s390xc[0-15]内核保留。
s390xa[0-1]系统使用保留。
PowerPC/PowerPC64r2, r13这些是系统保留的寄存器。
PowerPC/PowerPC64vrsavevrsave 寄存器不能用作输入或输出。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// bp is reserved
unsafe { core::arch::asm!("", in("bp") 5i32); }
// ERROR: invalid register `bp`: the frame pointer cannot be used as an operand for inline asm
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

帧指针和基指针寄存器保留供 LLVM 内部使用。虽然 asm! 语句不能显式指定使用保留寄存器,但在某些情况下 LLVM 将为 reg 操作数分配这些保留寄存器之一。使用保留寄存器的汇编代码应小心,因为 reg 操作数可能使用相同的寄存器。

模板修饰符

占位符可以通过在花括号中 : 之后指定的修饰符来增强。这些修饰符不影响寄存器分配,但会更改操作数插入模板字符串时的格式化方式。

每个模板占位符只允许一个修饰符。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// We can't specify both `r` and `e` at the same time.
unsafe { core::arch::asm!("/* {:er}", in(reg) 5i32); }
// ERROR: asm template modifier must be a single character
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

支持的修饰符是 LLVM(和 GCC)[asm 模板参数修饰符]asm template argument modifiers的子集,但不使用相同的字母代码。

架构寄存器类修饰符示例输出LLVM 修饰符
x86-32regeaxk
x86-64regraxq
x86-32reg_abcdlalb
x86-64reglalb
x86reg_abcdhahh
x86regxaxw
x86regeeaxk
x86-64regrraxq
x86reg_byteal / ah
x86xmm_regxmm0x
x86ymm_regymm0t
x86zmm_regzmm0g
x86*mm_regxxmm0x
x86*mm_regyymm0t
x86*mm_regzzmm0g
x86kregk1
AArch64/Arm64ECregx0x
AArch64/Arm64ECregww0w
AArch64/Arm64ECregxx0x
AArch64/Arm64ECvregv0
AArch64/Arm64ECvregvv0
AArch64/Arm64ECvregbb0b
AArch64/Arm64ECvreghh0h
AArch64/Arm64ECvregss0s
AArch64/Arm64ECvregdd0d
AArch64/Arm64ECvregqq0q
ARMregr0
ARMsregs0
ARMdregd0P
ARMqregq0q
ARMqrege / fd0 / d1e / f
RISC-Vregx1
RISC-Vfregf0
LoongArchreg$r1
LoongArchfreg$f0
s390xreg%r0
s390xreg_addr%r1
s390xfreg%f0
s390xvreg%v0
PowerPC/PowerPC64reg0
PowerPC/PowerPC64reg_nonzero3
PowerPC/PowerPC64freg0
PowerPC/PowerPC64vreg0
PowerPC/PowerPC64vsreg0

Note

  • 在 ARM 上 e / f:这打印 NEON 四字(128 位)寄存器的低或高双字寄存器名称。
  • 在 x86 上:我们对没有修饰符的 reg 的行为与 GCC 不同。GCC 将根据操作数值类型推断修饰符,而我们默认为完整的寄存器大小。
  • 在 x86 xmm_reg 上:xtg LLVM 修饰符尚未在 LLVM 中实现(它们仅受 GCC 支持),但这应该是一个简单的更改。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x = 0x10u16;

// u16::swap_bytes using `xchg`
// low half of `{x}` is referred to by `{x:l}`, and the high half by `{x:h}`
unsafe { core::arch::asm!("xchg {x:l}, {x:h}", x = inout(reg_abcd) x); }
assert_eq!(x, 0x1000u16);
}
}

如前一节所述,传递小于寄存器宽度的输入值将导致寄存器的高位包含未定义的值。如果内联汇编仅访问寄存器的低位,这不会成为问题,这可以通过使用模板修饰符在汇编代码中使用子寄存器名称(例如 ax 而不是 rax)来完成。由于这是一个容易犯的错误,编译器会根据输入类型在适当的情况下建议使用模板修饰符。如果对某个操作数的所有引用都已具有修饰符,则会抑制该操作数的警告。

ABI clobber

clobber_abi 关键字可用于向汇编代码应用默认的 clobber 集。这将自动插入必要的 clobber 约束,以便使用特定调用约定调用函数:如果调用约定不能在调用期间完全保留寄存器的值,则隐式添加 lateout("...") _ 到操作数列表(其中 ... 替换为寄存器的名称)。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo() -> i32 { 0 }

let z: i32;
// To call a function, we have to inform the compiler that we're clobbering
// callee saved registers
unsafe { core::arch::asm!("call {}", sym foo, out("rax") z, clobber_abi("C")); }
assert_eq!(z, 0);
}
}

clobber_abi 可以指定任意次数。它将为所有指定调用约定的并集中的所有唯一寄存器插入 clobber。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "sysv64" fn foo() -> i32 { 0 }
extern "win64" fn bar(x: i32) -> i32 { x + 1 }

let z: i32;
// We can even call multiple functions with different conventions and
// different saved registers
unsafe {
    core::arch::asm!(
        "call {}",
        "mov ecx, eax",
        "call {}",
        sym foo,
        sym bar,
        out("rax") z,
        clobber_abi("sysv64"),
        clobber_abi("win64"),
    );
}
assert_eq!(z, 1);
}
}

当使用 clobber_abi 时,编译器禁止使用通用寄存器类输出:所有输出必须指定显式寄存器。

#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo(x: i32) -> i32 { 0 }

let z: i32;
// explicit registers must be used to not accidentally overlap.
unsafe {
    core::arch::asm!(
        "mov eax, {:e}",
        "call {}",
        out(reg) z,
        sym foo,
        clobber_abi("C")
    );
    // ERROR: asm with `clobber_abi` must specify explicit registers for outputs
}
assert_eq!(z, 0);
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}

显式寄存器输出优先于 clobber_abi 插入的隐式 clobber:只有当寄存器未用作输出时,才会为该寄存器插入 clobber。

以下 ABI 可与 clobber_abi 一起使用:

架构ABI 名称Clobber 的寄存器
x86-32"C", "system", "efiapi", "cdecl", "stdcall", "fastcall"ax, cx, dx, xmm[0-7], mm[0-7], k[0-7], st([0-7])
x86-64"C", "system"(在 Windows 上), "efiapi", "win64"ax, cx, dx, r[8-11], xmm[0-31], mm[0-7], k[0-7], st([0-7]), tmm[0-7]
x86-64"C", "system"(在非 Windows 上), "sysv64"ax, cx, dx, si, di, r[8-11], xmm[0-31], mm[0-7], k[0-7], st([0-7]), tmm[0-7]
AArch64"C", "system", "efiapi"x[0-17], x18*, x30, v[0-31], p[0-15], ffr
Arm64EC"C", "system"x[0-12], x[15-17], x30, v[0-15]
ARM"C", "system", "efiapi", "aapcs"r[0-3], r12, r14, s[0-15], d[0-7], d[16-31]
RISC-V"C", "system", "efiapi"x1, x[5-7], x[10-17]*, x[28-31]*, f[0-7], f[10-17], f[28-31], v[0-31]
LoongArch"C", "system"$r1, $r[4-20], $f[0-23]
s390x"C", "system"r[0-5], r14, f[0-7], v[0-31], a[2-15]

Note

  • 在 AArch64 上,只有当 x18 在目标上不被视为保留寄存器时,才包含在 clobber 列表中。
  • 在 RISC-V 上,只有当 x[16-17]x[28-31] 在目标上不被视为保留寄存器时,才包含在 clobber 列表中。

每个 ABI 的 clobber 寄存器列表在 rustc 中随着架构获得新寄存器而更新:这确保当 LLVM 开始在其生成的代码中使用这些新寄存器时,asm! clobber 将继续正确。

选项

标志用于进一步影响内联汇编代码的行为。目前定义了以下选项:

  • pure:汇编代码没有副作用,必须最终返回,并且其输出仅取决于其直接输入(即值本身,而不是它们指向的内容)或从内存读取的值(除非同时设置了 nomem 选项)。这允许编译器执行汇编代码的次数少于程序中指定的次数(例如通过将其从循环中提升出来),或者在输出未使用时完全消除它。pure 选项必须与 nomemreadonly 选项组合使用,否则会发出编译时错误。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let z: i32;
// pure can be used to optimize by assuming the assembly has no side effects
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure, nomem)); }
assert_eq!(z, 1);
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let z: i32;
// Either nomem or readonly must be satisfied, to indicate whether or not
// memory is allowed to be read
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure)); }
// ERROR: the `pure` option must be combined with either `nomem` or `readonly`
assert_eq!(z, 0);
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}
  • nomem:汇编代码不读取或写入汇编代码外部可访问的任何内存。这允许编译器在汇编代码执行期间缓存修改的全局变量的值,因为它知道它们不会被读取或写入。编译器还假设汇编代码不执行任何与其他线程的同步,例如通过屏障。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x = 0i32;
let z: i32;
// Accessing outside memory from assembly when `nomem` is
// specified is disallowed
unsafe {
    core::arch::asm!("mov {val:e}, dword ptr [{ptr}]",
        ptr = in(reg) &mut x,
        val = lateout(reg) z,
        options(nomem)
    )
}

// Writing to outside memory from assembly when `nomem` is
// specified is also undefined behaviour
unsafe {
    core::arch::asm!("mov  dword ptr [{ptr}], {val:e}",
        ptr = in(reg) &mut x,
        val = in(reg) z,
        options(nomem)
    )
}
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let z: i32;
// If we allocate our own memory, such as via `push`, however.
// we can still use it
unsafe {
    core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}",
        x = inout(reg) x => z,
        options(nomem)
    );
}
assert_eq!(z, 1);
}
}
  • readonly:汇编代码不写入汇编代码外部可访问的任何内存。这允许编译器在汇编代码执行期间缓存未修改的全局变量的值,因为它知道它们不会被写入。编译器还假设此汇编代码不执行任何与其他线程的同步,例如通过屏障。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x = 0;
// We cannot modify outside memory when `readonly` is specified
unsafe {
    core::arch::asm!("mov dword ptr[{}], 1", in(reg) &mut x, options(readonly))
}
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64 = 0;
let z: i64;
// We can still read from it, though
unsafe {
    core::arch::asm!("mov {x}, qword ptr [{x}]",
        x = inout(reg) &x => z,
        options(readonly)
    );
}
assert_eq!(z, 0);
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64 = 0;
let z: i64;
// Same exception applies as with nomem.
unsafe {
    core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}",
        x = inout(reg) x => z,
        options(readonly)
    );
}
assert_eq!(z, 1);
}
}
  • preserves_flags:汇编代码不修改标志寄存器(在下面的规则中定义)。这允许编译器避免在执行汇编代码后重新计算条件标志。
  • noreturn:汇编代码不会穿透;如果穿透则行为未定义。它仍然可以跳转到 label 块。如果任何 label 块返回单元,则 asm! 块将返回单元。否则它将返回 !(永不)。与调用不返回的函数一样,作用域内的局部变量在执行汇编代码之前不会被丢弃。
fn main() -> ! {
#[cfg(target_arch = "x86_64")] {
    // We can use an instruction to trap execution inside of a noreturn block
    unsafe { core::arch::asm!("ud2", options(noreturn)); }
}
#[cfg(not(target_arch = "x86_64"))] panic!("no return");
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// You are responsible for not falling past the end of a noreturn asm block
unsafe { core::arch::asm!("", options(noreturn)); }
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")]
let _: () = unsafe {
    // You may still jump to a `label` block
    core::arch::asm!("jmp {}", label {
        println!();
    }, options(noreturn));
};
}
  • nostack:汇编代码不将数据推送到栈上,或写入栈红区(如果目标支持)。如果使用此选项,则编译器保证在汇编代码开头栈指针适合对齐(根据目标 ABI)以进行函数调用。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// `push` and `pop` are UB when used with nostack
unsafe { core::arch::asm!("push rax", "pop rax", options(nostack)); }
}
}
  • att_syntax:此选项仅在 x86 上有效,并导致汇编器使用 GNU 汇编器的 .att_syntax prefix 模式。寄存器操作数以前导 % 替换。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32;
let y = 1i32;
// We need to use AT&T Syntax here. src, dest order for operands
unsafe {
    core::arch::asm!("mov {y:e}, {x:e}",
        x = lateout(reg) x,
        y = in(reg) y,
        options(att_syntax)
    );
}
assert_eq!(x, y);
}
}
  • raw:这导致模板字符串被解析为原始汇编字符串,对 {} 没有特殊处理。这在使用 include_str! 从外部文件包含原始汇编代码时主要有用。

编译器对选项执行一些额外的检查:

  • nomemreadonly 选项是互斥的:同时指定两者是编译时错误。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// nomem is strictly stronger than readonly, they can't be specified together
unsafe { core::arch::asm!("", options(nomem, readonly)); }
// ERROR: the `nomem` and `readonly` options are mutually exclusive
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}
  • 在没有输出或只有丢弃输出(_)的 asm 块上指定 pure 是编译时错误。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// pure blocks need at least one output
unsafe { core::arch::asm!("", options(pure)); }
// ERROR: asm with the `pure` option must have at least one output
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}
  • 在有输出且没有标签的 asm 块上指定 noreturn 是编译时错误。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let z: i32;
// noreturn can't have outputs
unsafe { core::arch::asm!("mov {:e}, 1", out(reg) z, options(noreturn)); }
// ERROR: asm outputs are not allowed with the `noreturn` option
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
}
  • 在有输出的 asm 块中有任何 label 块是编译时错误。

naked_asm! 仅支持 att_syntaxraw 选项。其余选项没有意义,因为内联汇编定义了整个函数主体。

global_asm! 仅支持 att_syntaxraw 选项。其余选项对全局作用域的内联汇编没有意义。

fn main() {}
#[cfg(target_arch = "x86_64")]
// nomem is useless on global_asm!
core::arch::global_asm!("", options(nomem));
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");

内联汇编规则

为避免未定义行为,使用函数作用域内联汇编(asm!)时必须遵循以下规则:

  • 未指定为输入的任何寄存器在进入汇编代码时将包含未定义的值。
    • 内联汇编上下文中的“未定义值“意味着寄存器可以(非确定地)具有架构允许的任何可能值。特别是,它与 LLVM 的 undef 不同,后者每次读取时可能具有不同的值(因为汇编代码中不存在这样的概念)。
  • 未指定为输出的任何寄存器在退出汇编代码时必须与进入时具有相同的值,否则行为未定义。
    • 这仅适用于可以指定为输入或输出的寄存器。其他寄存器遵循特定于目标的规则。
    • 请注意,lateout 可能分配到与 in 相同的寄存器,在这种情况下此规则不适用。但是,代码不应依赖于此,因为它取决于寄存器分配的结果。
  • 如果执行从汇编代码展开,则行为未定义。
    • 如果汇编代码调用的函数随后展开,这也适用。
  • 汇编代码允许读取和写入的内存位置集与 FFI 函数允许的相同。
    • 如果设置了 readonly 选项,则只允许内存读取。
    • 如果设置了 nomem 选项,则不允许读取或写入内存。
    • 这些规则不适用于汇编代码私有的内存,例如在其中分配的栈空间。
  • 编译器不能假设汇编代码中的指令是实际将执行的指令。
    • 这实际上意味着编译器必须将汇编代码视为黑盒,只考虑接口规范,而不是指令本身。
    • 允许通过特定于目标的机制进行运行时代码修补。
    • 但是,不能保证源代码中的每个汇编代码块直接对应于目标文件中的单个指令实例;编译器可以自由地复制或去重 asm! 块中的汇编代码。
  • 除非设置了 nostack 选项,否则汇编代码允许使用栈指针下方的栈空间。
    • 进入汇编代码时,栈指针保证适合对齐(根据目标 ABI)以进行函数调用。
    • 您有责任确保不会溢出栈(例如使用栈探测以确保触及保护页)。
    • 分配栈内存时,应根据目标 ABI 的要求调整栈指针。
    • 离开汇编代码之前,必须将栈指针恢复到其原始值。
  • 除非设置了 nostack 选项,否则当目标 ABI 要求在调用者的帧中存储某些值时(例如在 PowerPC64 上保存 lr 时),汇编代码可以修改调用者的栈帧。
  • 如果设置了 noreturn 选项,则执行穿透汇编代码末尾时行为未定义。
  • 如果设置了 pure 选项,则如果 asm! 具有除其直接输出之外的副作用,则行为未定义。如果两次使用相同输入执行 asm! 代码导致不同输出,行为也未定义。
    • nomem 选项一起使用时,“输入“只是 asm! 的直接输入。
    • readonly 选项一起使用时,“输入“包括汇编代码的直接输入和允许读取的任何内存。
  • 如果设置了 preserves_flags 选项,则这些标志寄存器必须在退出汇编代码时恢复:
    • x86
      • EFLAGS 中的状态标志(CF、PF、AF、ZF、SF、OF)。
      • 浮点状态字(全部)。
      • MXCSR 中的浮点异常标志(PE、UE、OE、ZE、DE、IE)。
    • ARM
      • CPSR 中的条件标志(N、Z、C、V)
      • CPSR 中的饱和标志(Q)
      • CPSR 中的大于或等于标志(GE)。
      • FPSCR 中的条件标志(N、Z、C、V)
      • FPSCR 中的饱和标志(QC)
      • FPSCR 中的浮点异常标志(IDC、IXC、UFC、OFC、DZC、IOC)。
    • AArch64 和 Arm64EC
      • 条件标志(NZCV 寄存器)。
      • 浮点状态(FPSR 寄存器)。
    • RISC-V
      • fcsr 中的浮点异常标志(fflags)。
      • 向量扩展状态(vtypevlvxsatvxrm)。
    • LoongArch
      • $fcc[0-7] 中的浮点条件标志。
    • PowerPC/PowerPC64
      • fpscr 中的浮点状态和粘滞位(除 DRN、VE、OE、UE、ZE、XE、NI 或 RN 之外的任何字段)。
      • vscr 中的向量状态和粘滞位(除 NJ 之外的任何字段)。
    • PowerPC SPE
      • spefscr 的粘滞和状态位(除 FINXE、FINVE、FDBZE、FUNFE、FOVFE 或 FRMC 之外的任何字段)。
    • s390x
      • 条件码寄存器 cc
  • 在 x86 上,方向标志(EFLAGS 中的 DF)在进入汇编代码时是清除的,并且在退出时必须是清除的。
    • 如果在退出汇编代码时方向标志被设置,则行为未定义。
  • 在 x86 上,x87 浮点寄存器栈必须保持不变,除非所有 st([0-7]) 寄存器都已被标记为 clobber(使用 out("st(0)") _, out("st(1)") _, ...)。
    • 如果所有 x87 寄存器都被 clobber,则保证在进入汇编代码时 x87 寄存器栈为空。汇编代码必须确保在退出汇编代码时 x87 寄存器栈也为空。
#[cfg(target_arch = "x86_64")]
pub fn fadd(x: f64, y: f64) -> f64 {
  let mut out = 0f64;
  let mut top = 0u16;
  // we can do complex stuff with x87 if we clobber the entire x87 stack
  unsafe { core::arch::asm!(
    "fld qword ptr [{x}]",
    "fld qword ptr [{y}])",
    "faddp",
    "fstp qword ptr [{out}]",
    "xor eax, eax",
    "fstsw ax",
    "shl eax, 11",
    x = in(reg) &x,
    y = in(reg) &y,
    out = in(reg) &mut out,
    out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
    out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
    out("eax") top
  );}

  assert_eq!(top & 0x7, 0);
  out
}

pub fn main() {
#[cfg(target_arch = "x86_64")]{
  assert_eq!(fadd(1.0, 1.0), 2.0);
}
}
  • 恢复栈指针和非输出寄存器到其原始值的要求仅在退出汇编代码时适用。
    • 这意味着不穿透且不跳转到任何 label 块的汇编代码,即使未标记为 noreturn,也不需要保留这些寄存器。
    • 当返回到与您进入的不同的 asm! 块的汇编代码时(例如用于上下文切换),这些寄存器必须包含您退出asm! 块进入时的值。
      • 您不能退出尚未进入的 asm! 块的汇编代码。您也不能退出已经退出的 asm! 块的汇编代码(除非先重新进入它)。
      • 您有责任切换任何特定于目标的状态(例如线程本地存储、栈边界)。
      • 您不能从一个 asm! 块中的地址跳转到另一个块中的地址,即使在同一函数或块内,而无需将其上下文视为可能不同并需要上下文切换。您不能假设这些上下文中的任何特定值(例如当前栈指针或栈指针下方的临时值)在两个 asm! 块之间保持不变。
      • 您可以访问的内存位置集是您进入和退出的 asm! 块允许的交集。
  • 您不能假设源代码中相邻的两个 asm! 块(即使它们之间没有其他代码)将在二进制文件中连续地址处,中间没有任何其他指令。
  • 您不能假设 asm! 块将恰好在输出二进制文件中出现一次。编译器允许实例化 asm! 块的多个副本,例如当包含它的函数在多个位置内联时。
  • 在 x86 上,内联汇编不得以指令前缀(如 LOCK)结尾,该前缀将应用于编译器生成的指令。
    • 由于内联汇编的编译方式,编译器目前无法检测到这一点,但将来可能会捕获并拒绝此情况。

Note

作为一般规则,preserves_flags 涵盖的标志是执行函数调用时保留的那些。

裸内联汇编规则

为避免未定义行为,在裸函数中使用函数作用域内联汇编(naked_asm!)时必须遵循以下规则:

  • 根据调用约定和函数签名,未用于函数输入的任何寄存器在进入 naked_asm! 块时将包含未定义的值。
    • 内联汇编上下文中的“未定义值“意味着寄存器可以(非确定地)具有架构允许的任何可能值。特别是,它与 LLVM 的 undef 不同,后者每次读取时可能具有不同的值(因为汇编代码中不存在这样的概念)。
  • 所有被调用者保存的寄存器在返回时必须与进入时具有相同的值。
  • 调用者保存的寄存器可以自由使用。
  • 执行穿透汇编代码末尾时行为未定义。
    • 汇编代码中的每条路径都应以返回指令终止或发散。
  • 汇编代码允许读取和写入的内存位置集与 FFI 函数允许的相同。
  • 编译器不能假设 naked_asm! 块中的指令是实际将执行的指令。
    • 这实际上意味着编译器必须将 naked_asm! 视为黑盒,只考虑接口规范,而不是指令本身。
    • 允许通过特定于目标的机制进行运行时代码修补。
  • 允许从 naked_asm! 块展开。
    • 为了正确行为,必须使用发出展开元数据的适当汇编器指令。
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
#[unsafe(naked)]
extern "sysv64-unwind" fn unwinding_naked() {
    core::arch::naked_asm!(
        // "CFI" here stands for "call frame information".
        ".cfi_startproc",
        // The CFA (canonical frame address) is the value of `rsp`
        // before the `call`, i.e. before the return address, `rip`,
        // was pushed to `rsp`, so it's eight bytes higher in memory
        // than `rsp` upon function entry (after `rip` has been
        // pushed).
        //
        // This is the default, so we don't have to write it.
        //".cfi_def_cfa rsp, 8",
        //
        // The traditional thing to do is to preserve the base
        // pointer, so we'll do that.
        "push rbp",
        // Since we've now extended the stack downward by 8 bytes in
        // memory, we need to adjust the offset to the CFA from `rsp`
        // by another 8 bytes.
        ".cfi_adjust_cfa_offset 8",
        // We also then annotate where we've stored the caller's value
        // of `rbp`, relative to the CFA, so that when unwinding into
        // the caller we can find it, in case we need it to calculate
        // the caller's CFA relative to it.
        //
        // Here, we've stored the caller's `rbp` starting 16 bytes
        // below the CFA.  I.e., starting from the CFA, there's first
        // the `rip` (which starts 8 bytes below the CFA and continues
        // up to it), then there's the caller's `rbp` that we just
        // pushed.
        ".cfi_offset rbp, -16",
        // As is traditional, we set the base pointer to the value of
        // the stack pointer.  This way, the base pointer stays the
        // same throughout the function body.
        "mov rbp, rsp",
        // We can now track the offset to the CFA from the base
        // pointer.  This means we don't need to make any further
        // adjustments until the end, as we don't change `rbp`.
        ".cfi_def_cfa_register rbp",
        // We can now call a function that may panic.
        "call {f}",
        // Upon return, we restore `rbp` in preparation for returning
        // ourselves.
        "pop rbp",
        // Now that we've restored `rbp`, we must specify the offset
        // to the CFA again in terms of `rsp`.
        ".cfi_def_cfa rsp, 8",
        // Now we can return.
        "ret",
        ".cfi_endproc",
        f = sym may_panic,
    )
}

extern "sysv64-unwind" fn may_panic() {
    panic!("unwind");
}
}
}

Note

有关上面 cfi 汇编器指令的更多信息,请参阅以下资源:

正确性和有效性

除了所有先前的规则外,asm! 的字符串参数最终必须成为——在所有其他参数被求值、执行格式化并将操作数转换之后——语法正确且对目标架构语义有效的汇编。格式化规则允许编译器生成语法正确的汇编。关于操作数的规则允许将 Rust 操作数有效转换到汇编代码中和从汇编代码中转换出来。遵守这些规则是必要的,但不足以使最终展开的汇编既正确又有效。例如:

  • 参数在格式化后可能被放置在语法不正确的位置
  • 指令可能编写正确,但被赋予架构上无效的操作数
  • 架构上未指定的指令可能被汇编为未指定的代码
  • 一组指令,每个都正确和有效,如果连续放置可能导致未定义行为

因此,这些规则是_非详尽的_。编译器不需要检查初始字符串或生成的最终汇编的正确性和有效性。汇编器可能检查正确性和有效性,但不被要求这样做。使用 asm! 时,拼写错误可能足以使程序不健全,汇编规则可能包含数千页的架构参考手册。程序员应适当小心,因为调用此 unsafe 功能意味着承担不违反编译器或架构规则的责任。

指令支持

内联汇编支持 GNU AS 和 LLVM 内部汇编器都支持的指令子集,如下所示。使用其他指令的结果是特定于汇编器的(可能导致错误,或可能被原样接受)。

如果内联汇编包含任何修改后续汇编处理方式的“有状态“指令,则汇编代码必须在内联汇编结束前撤消任何此类指令的效果。

以下指令保证被汇编器支持:

  • .2byte
  • .4byte
  • .8byte
  • .align
  • .alt_entry
  • .ascii
  • .asciz
  • .balign
  • .balignl
  • .balignw
  • .bss
  • .byte
  • .comm
  • .data
  • .def
  • .double
  • .endef
  • .equ
  • .equiv
  • .eqv
  • .fill
  • .float
  • .global
  • .globl
  • .inst
  • .insn
  • .lcomm
  • .long
  • .octa
  • .option
  • .p2align
  • .popsection
  • .private_extern
  • .pushsection
  • .quad
  • .scl
  • .section
  • .set
  • .short
  • .size
  • .skip
  • .sleb128
  • .space
  • .string
  • .text
  • .type
  • .uleb128
  • .word
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let bytes: *const u8;
let len: usize;
unsafe {
    core::arch::asm!(
        "jmp 3f", "2: .ascii \"Hello World!\"",
        "3: lea {bytes}, [2b+rip]",
        "mov {len}, 12",
        bytes = out(reg) bytes,
        len = out(reg) len
    );
}

let s = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(bytes, len)) };

assert_eq!(s, "Hello World!");
}
}

特定于目标的指令支持

Dwarf 展开

以下指令在支持 DWARF 展开信息的 ELF 目标上受支持:

  • .cfi_adjust_cfa_offset
  • .cfi_def_cfa
  • .cfi_def_cfa_offset
  • .cfi_def_cfa_register
  • .cfi_endproc
  • .cfi_escape
  • .cfi_lsda
  • .cfi_offset
  • .cfi_personality
  • .cfi_register
  • .cfi_rel_offset
  • .cfi_remember_state
  • .cfi_restore
  • .cfi_restore_state
  • .cfi_return_column
  • .cfi_same_value
  • .cfi_sections
  • .cfi_signal_frame
  • .cfi_startproc
  • .cfi_undefined
  • .cfi_window_save
结构化异常处理

在具有结构化异常处理的目标上,保证支持以下附加指令:

  • .seh_endproc
  • .seh_endprologue
  • .seh_proc
  • .seh_pushreg
  • .seh_savereg
  • .seh_setframe
  • .seh_stackalloc
x86(32 位和 64 位)

在 x86 目标上(32 位和 64 位),保证支持以下附加指令:

  • .nops
  • .code16
  • .code32
  • .code64

只有在退出汇编代码之前将状态重置为默认值时,才支持使用 .code16.code32.code64 指令。32 位 x86 默认使用 .code32,x86_64 默认使用 .code64

ARM(32 位)

在 ARM 上,保证支持以下附加指令:

  • .even
  • .fnstart
  • .fnend
  • .save
  • .movsp
  • .code
  • .thumb
  • .thumb_func

不安全

不安全操作是那些可能违反 Rust 静态语义的内存安全保证的操作。

以下语言级功能不能在 Rust 的安全子集中使用:

  • 访问 union 的字段,赋值除外。
  • 调用不安全函数。

  1. 在 2024 版本之前,允许声明不带 unsafe 的 extern 块。

unsafe 关键字

unsafe 关键字用于创建或解除证明某事安全的义务。具体来说:

  • 它用于标记定义必须在其他地方维护的额外安全条件的代码。
    • 这包括 unsafe fnunsafe staticunsafe trait
  • 它用于标记程序员断言满足在其他地方定义的安全条件的代码。
    • 这包括 unsafe {}unsafe impl、没有 unsafe_op_in_unsafe_fnunsafe fnunsafe extern#[unsafe(attr)]

以下讨论了每种情况。有关一些说明性示例,请参阅关键字文档

unsafe 关键字可以出现在几种不同的上下文中:

  • 不安全函数(unsafe fn
  • 不安全块(unsafe {}
  • 不安全 trait(unsafe trait
  • 不安全 trait 实现(unsafe impl
  • 不安全外部块(unsafe extern
  • 不安全外部静态(unsafe static
  • 不安全属性(#[unsafe(attr)]

不安全函数(unsafe fn

不安全函数是在所有上下文中和/或对所有可能输入不安全的函数。我们说它们具有额外安全条件,这是所有调用者必须维护的要求,编译器不会检查。例如,get_unchecked 具有额外安全条件,即索引必须在边界内。不安全函数应附带解释这些额外安全条件是什么的文档。

此类函数必须以关键字 unsafe 为前缀,并且只能从 unsafe 块内部调用,或在没有 unsafe_op_in_unsafe_fn lint 的 unsafe fn 内部调用。

不安全块(unsafe {}

代码块可以用 unsafe 关键字前缀,以允许使用不安全章节中定义的不安全操作,例如调用其他不安全函数或解引用裸指针。

默认情况下,不安全函数的主体也被视为不安全块;这可以通过启用 unsafe_op_in_unsafe_fn lint 来更改。

通过将操作放入不安全块,程序员声明他们已注意满足该块内所有操作的额外安全条件。

不安全块是不安全函数的逻辑对偶:不安全函数定义了调用者必须维护的证明义务,不安全块声明块内调用的函数或操作的所有相关证明义务已被解除。有许多方式可以解除证明义务;例如,可能有运行时检查或数据结构不变量保证某些属性肯定为真,或者不安全块可能在 unsafe fn 内,在这种情况下,该块可以使用该函数的证明义务来解除块内产生的证明义务。

不安全块用于包装外部库、直接使用硬件或实现语言中不存在的功能。例如,Rust 提供了在语言中实现内存安全并发所需的语言功能,但标准库中线程和消息传递的实现使用了不安全块。

Rust 的类型系统是动态安全需求的保守近似,因此在某些情况下使用安全代码会有性能成本。例如,双向链表不是树结构,在安全代码中只能用引用计数指针表示。通过使用 unsafe 块将反向链接表示为裸指针,可以在没有引用计数的情况下实现。(有关此特定示例的更深入探讨,请参阅“通过太多链表学习 Rust”。)

不安全 trait(unsafe trait

不安全 trait 是具有额外安全条件的 trait,必须由 trait 的实现维护。不安全 trait 应附带解释这些额外安全条件是什么的文档。

此类 trait 必须以关键字 unsafe 为前缀,并且只能由 unsafe impl 块实现。

不安全 trait 实现(unsafe impl

实现不安全 trait 时,实现需要以 unsafe 关键字为前缀。通过编写 unsafe impl,程序员声明他们已注意满足 trait 所需的额外安全条件。

不安全 trait 实现是不安全 trait 的逻辑对偶:不安全 trait 定义了实现必须维护的证明义务,不安全实现声明所有相关证明义务已被解除。

不安全外部块(unsafe extern

声明外部块的程序员必须确保其中包含的项的签名是正确的。未能这样做可能导致未定义行为。通过编写 unsafe extern 来指示已满足此义务。

2024 Edition differences

在 2024 版本之前,允许 extern 块不需要限定为 unsafe

不安全属性(#[unsafe(attr)]

不安全属性是使用属性时必须维护额外安全条件的属性。编译器无法检查这些条件是否已满足。为了断言它们已满足,这些属性必须包装在 unsafe(..) 中,例如 #[unsafe(no_mangle)]

被视为未定义的行为

如果 Rust 代码表现出以下列表中的任何行为,则该代码是错误的。这包括 unsafe 块和 unsafe 函数中的代码。unsafe 仅意味着避免未定义行为的责任在于程序员;它不会改变 Rust 程序绝不应导致未定义行为的事实。

编写 unsafe 代码时,程序员有责任确保与 unsafe 代码交互的任何安全代码都无法触发这些行为。满足任何安全客户端此属性的 unsafe 代码称为健全的;如果 unsafe 代码可以被安全代码误用以表现出未定义行为,则它是不健全的

Warning

以下列表并非详尽无遗;它可能会增长或缩小。Rust 的语义对于不安全代码中允许和不允许的内容没有正式模型,因此可能有更多行为被视为不安全。我们还保留将来使该列表中的某些行为成为已定义的权利。换句话说,此列表并不表示任何内容在所有未来的 Rust 版本中肯定始终是未定义的(但我们将来可能会对某些列表项做出此类承诺)。

编写不安全代码之前,请阅读 Rustonomicon

  • 数据竞争。
  • 违反指针别名规则。确切的别名规则尚未确定,但以下是一般原则的概述:

    &T 必须指向在其存活期间不被修改的内存(UnsafeCell<U> 内部的数据除外),而 &mut T 必须指向不被任何非从该引用派生的指针读取或写入的内存,并且在其存活期间没有其他引用指向。Box<T> 在这些规则的目的上被视为类似于 &'static mut T。确切的存活持续时间未指定,但存在一些界限:

    • 对于引用,存活持续时间的上限是借用检查器分配的语法生命周期;它不能比该生命周期存活更长
    • 每次引用或 box 被解引用或重新借用时,它都被视为存活。
    • 每次引用或 box 被传递给函数或从函数返回时,它都被视为存活。
    • 当引用(但不是 Box!)被传递给函数时,它至少在该函数调用期间存活,除非 &T 包含 UnsafeCell<U>

    当这些类型的值在复合类型的(嵌套)字段中传递时,所有这些也适用,但在指针间接之后则不适用。

  • 修改不可变字节。通过常量提升表达式可到达的所有字节都是不可变的,通过 staticconst 初始化器中的借用可到达的字节也是如此,这些借用已被生命周期扩展'static。由不可变绑定或不可变 static 拥有的字节是不可变的,除非这些字节是 UnsafeCell<U> 的一部分。

    此外,由共享引用指向的字节,包括通过其他引用(共享和可变)和 Box 传递地指向的字节,是不可变的;传递性包括存储在复合类型字段中的引用。

    修改是写入超过 0 字节的任何写入,与任何相关字节重叠(即使该写入不更改内存内容)。

  • 通过编译器内部函数调用未定义行为。
  • 执行使用当前平台不支持的平台功能编译的代码(参见 target_feature),除非平台明确记录这是安全的。
  • 使用错误的调用 ABI 调用函数,或展开超过不允许展开的栈帧(例如,通过调用导入或转换为 "C" 函数或函数指针的 "C-unwind" 函数)。
  • 产生无效值。“产生“值发生在值被赋值到位置或从位置读取、传递给函数/原始操作或从函数/原始操作返回时。
  • 错误使用内联汇编。有关更多详细信息,请参阅编写使用内联汇编代码时要遵循的规则
  • 违反 Rust 运行时的假设。Rust 运行时的大多数假设目前没有明确记录。
    • 有关与展开相关的假设,请参阅 panic 文档
    • 运行时假定 Rust 栈帧不会在不执行栈帧拥有的局部变量的析构器的情况下被释放。此假设可以被 longjmp 等 C 函数违反。

Note

未定义行为影响整个程序。例如,调用 C 中表现出 C 未定义行为的函数意味着您的整个程序包含也可能影响 Rust 代码的未定义行为。反之,Rust 中的未定义行为可能对通过任何 FFI 调用其他语言执行的代码产生不利影响。

指向的字节

指针或引用“指向“的字节范围由指针值和被指向类型的大小(使用 size_of_val)确定。

基于未对齐指针的位置

如果位置计算期间的最后一个 * 投影是在未对其类型对齐的指针上执行的,则该位置被称为“基于未对齐指针“。(如果位置表达式中没有 * 投影,则这是访问局部变量或 static 的字段,rustc 将保证正确对齐。如果有多个 * 投影,则每个投影都会导致从内存加载要解引用的指针本身,并且每个加载都受对齐约束。请注意,由于自动解引用,某些 * 投影可以在表面 Rust 语法中省略;我们在这里考虑完全展开的位置表达式。)

例如,如果 ptr 类型为 *const S,其中 S 的对齐为 8,则 ptr 必须是 8 对齐的,否则 (*ptr).f 是“基于未对齐指针“的。即使字段 f 的类型是 u8(即对齐为 1 的类型),也是如此。换句话说,对齐要求源自被解引用的指针类型,而不是被访问的字段类型。

请注意,基于未对齐指针的位置仅在加载或存储时导致未定义行为。

在此类位置上允许使用 &raw const/&raw mut

位置上的 &/&mut 需要字段类型的对齐(否则程序将“产生无效值“),这通常比基于对齐指针的要求更宽松。

在字段类型可能比包含它的类型更对齐的情况下(即 repr(packed)),获取引用将导致编译器错误。这意味着基于对齐指针始终足以确保新引用是对齐的,但这并非总是必要的。

悬垂指针

如果引用/指针指向的所有字节不都是同一存活分配的一部分(因此它们都必须是某个分配的一部分),则该引用/指针是“悬垂的“。

如果大小为 0,则指针显然永远不会“悬垂“(即使它是空指针)。

请注意,动态大小类型(如切片和字符串)指向其整个范围,因此长度元数据永远不能太大。

特别是,Rust 值的动态大小(由 size_of_val 确定)绝不能超过 isize::MAX,因为单个分配不可能大于 isize::MAX

无效值

Rust 编译器假定程序执行期间产生的所有值都是“有效的“,因此产生无效值是立即的 UB。

值是否有效取决于类型:

  • bool 值必须是 false0)或 true1)。
  • fn 指针值必须非空。
  • char 值不得是代理项(即不得在范围 0xD800..=0xDFFF 内)且必须等于或小于 char::MAX
  • ! 值绝不能存在。
  • 整数(i*/u*)、浮点值(f*)或裸指针必须已初始化,即不得从未初始化内存获取。
  • str 值被视为 [u8],即必须已初始化。
  • enum 必须具有有效的判别值,该判别值指示的变体的所有字段必须在其各自的类型处有效。
  • struct、元组和数组要求所有字段/元素在其各自的类型处有效。
  • 对于 union,确切的有效性要求尚未决定。显然,可以在安全代码中完全创建的所有值都是有效的。如果联合体具有零大小字段,则每个可能的值都是有效的。更多细节仍在讨论中
  • 引用或 Box<T> 必须是对齐的且非空,它不能是悬垂的,并且必须指向有效值(对于动态大小类型,使用由元数据确定的被指向者的实际动态类型)。请注意,最后一点(关于指向有效值)仍是一些争论的主题。
  • 宽引用、Box<T> 或裸指针的元数据必须与未大小尾部的类型匹配:
    • dyn Trait 元数据必须是指向 Trait 的编译器生成的 vtable 的指针。(对于裸指针,此要求仍是一些争论的主题。)
    • 切片([T])元数据必须是有效的 usize。此外,对于宽引用和 Box<T>,如果切片元数据使被指向值的总大小大于 isize::MAX,则切片元数据无效。
  • 如果类型具有自定义的有效值范围,则有效值必须在该范围内。在标准库中,这影响 NonNull<T>NonZero<T>

    Note

    rustc 使用不稳定的 rustc_layout_scalar_valid_range_* 属性实现此目的。

  • 常量上下文:除了上面描述的内容之外,在常量求值期间还适用与来源相关的进一步要求。任何持有纯整数数据的值(i*/u*/f* 类型以及 boolchar、枚举判别值和切片元数据)不得携带任何来源。任何持有指针数据的值(引用、裸指针、函数指针和 dyn Trait 元数据)必须不携带来源,或者所有字节必须是同一原始指针值的片段且顺序正确。

    这意味着如果指针具有来源,将指针(引用、裸指针或函数指针)转换或重新解释为非指针类型(如整数)是未定义行为。

    Example

    以下都是 UB:

    #![allow(unused)]
    fn main() {
    use core::mem::MaybeUninit;
    use core::ptr;
    // We cannot reinterpret a pointer with provenance as an integer,
    // as then the bytes of the integer will have provenance.
    const _: usize = {
        let ptr = &0;
        unsafe { (&raw const ptr as *const usize).read() }
    };
    
    // We cannot rearrange the bytes of a pointer with provenance and
    // then interpret them as a reference, as then a value holding
    // pointer data will have pointer fragments in the wrong order.
    const _: &i32 = {
        let mut ptr = &0;
        let ptr_bytes = &raw mut ptr as *mut MaybeUninit::<u8>;
        unsafe { ptr::swap(ptr_bytes.add(1), ptr_bytes.add(2)) };
        ptr
    };
    }

**注意:**对于具有受限有效值集的任何类型,未初始化的内存也隐式无效。换句话说,允许读取未初始化内存的唯一情况是在 union 内部和“填充“(类型字段之间的间隙)中。

不被视为 unsafe 的行为

Rust 编译器不认为以下行为是_不安全的_,尽管程序员可能(应该)认为它们是不受欢迎的、意外的或错误的。

  • 死锁
  • 内存和其他资源泄漏
  • 不调用析构器就退出
  • 通过指针泄漏暴露随机化基地址

整数溢出

如果程序包含算术溢出,则程序员犯了错误。在以下讨论中,我们保持算术溢出和包装算术之间的区别。第一个是错误的,而第二个是有意的。

当程序员启用了 debug_assert! 断言时(例如,通过启用非优化构建),实现必须插入在溢出时 panic 的动态检查。其他类型的构建可能导致 panic 或溢出时静默包装的值,由实现自行决定。

在隐式包装溢出的情况下,实现必须通过使用二进制补码溢出约定提供明确定义的(即使仍被视为错误的)结果。

整数类型提供固有方法,允许程序员显式执行包装算术。例如,i32::wrapping_add 提供二进制补码包装加法。

标准库还提供了一个 Wrapping<T> 新类型,确保 T 的所有标准算术操作都具有包装语义。

有关错误条件、理由和有关整数溢出的更多详细信息,请参阅 RFC 560

逻辑错误

安全代码可能施加额外的逻辑约束,这些约束既不能在编译时也不能在运行时检查。如果程序违反此类约束,行为可能未指定,但不会导致未定义行为。这可能包括 panic、错误结果、中止和不终止。行为也可能在运行、构建或构建类型之间有所不同。

例如,实现 HashEq 要求被认为相等的值具有相等的哈希值。另一个示例是 BinaryHeapBTreeMapBTreeSetHashMapHashSet 等数据结构,它们描述了键在数据结构中的修改约束。违反此类约束不被视为不安全,但程序被视为错误,其行为不可预测。

常量求值

常量求值是在编译期间计算表达式结果的过程。只有一部分表达式可以在编译时求值。

常量表达式

某些形式的表达式(称为常量表达式)可以在编译时求值。

常量上下文中的表达式必须是常量表达式。

常量上下文中的表达式始终在编译时求值。

在常量上下文之外,常量表达式可能在编译时求值,但不保证。

如果值必须在编译时求值(即在常量上下文中),则越界数组索引溢出等行为是编译器错误。否则,这些行为是警告,但可能在运行时 panic。

以下表达式是常量表达式,只要任何操作数也是常量表达式且不会导致任何 Drop::drop 调用运行。

  • 静态的路径,有以下限制:

    • 在任何常量求值上下文中不允许写入 static 项。
    • 在任何常量求值上下文中不允许读取 extern 静态。
    • 如果求值不是static 项的初始化器中进行的,则不允许读取任何可变 static。可变 staticstatic mut 项或具有内部可变类型的 static 项。

    这些要求仅在常量被求值时检查。换句话说,只要它们从未被执行,在常量上下文中语法上出现此类访问是允许的。

  • 所有形式的借用,包括原始借用,但以下情况除外:其临时作用域将被扩展(参见临时生命周期扩展)到程序末尾的表达式的借用,并且它们是:

    • 可变借用。
    • 导致具有内部可变性的值的表达式的共享借用。
    #![allow(unused)]
    fn main() {
    // Due to being in tail position, this borrow extends the scope of the
    // temporary to the end of the program. Since the borrow is mutable,
    // this is not allowed in a const expression.
    const C: &u8 = &mut 0; // ERROR not allowed
    }
    #![allow(unused)]
    fn main() {
    // Const blocks are similar to initializers of `const` items.
    let _: &u8 = const { &mut 0 }; // ERROR not allowed
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // This is not allowed as 1) the temporary scope is extended to the
    // end of the program and 2) the temporary has interior mutability.
    const C: &AtomicU8 = &AtomicU8::new(0); // ERROR not allowed
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // As above.
    let _: &_ = const { &AtomicU8::new(0) }; // ERROR not allowed
    }
    #![allow(unused)]
    fn main() {
    #![allow(static_mut_refs)]
    // Even though this borrow is mutable, it's not of a temporary, so
    // this is allowed.
    const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // Even though this borrow is of a value with interior mutability,
    // it's not of a temporary, so this is allowed.
    const C: &AtomicU8 = {
        static S: AtomicU8 = AtomicU8::new(0); &S // OK
    };
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // This shared borrow of an interior mutable temporary is allowed
    // because its scope is not extended.
    const C: () = { _ = &AtomicU8::new(0); }; // OK
    }
    #![allow(unused)]
    fn main() {
    // Even though the borrow is mutable and the temporary lives to the
    // end of the program due to promotion, this is allowed because the
    // borrow is not in tail position and so the scope of the temporary
    // is not extended via temporary lifetime extension.
    const C: () = { let _: &'static mut [u8] = &mut []; }; // OK
    //                                              ~~
    //                                     Promoted temporary.
    }

    Note

    换句话说 — 关注什么是允许的而不是什么是不允许的 — 内部可变数据的共享借用和可变借用仅在借用的位置表达式瞬态的间接的静态的时才在常量上下文中允许。

    位置表达式是瞬态的,如果它是当前常量上下文的局部变量或其临时作用域包含在当前常量上下文中的表达式。

    #![allow(unused)]
    fn main() {
    // The borrow is of a variable local to the initializer, therefore
    // this place expression is transient.
    const C: () = { let mut x = 0; _ = &mut x; };
    }
    #![allow(unused)]
    fn main() {
    // The borrow is of a temporary whose scope has not been extended,
    // therefore this place expression is transient.
    const C: () = { _ = &mut 0u8; };
    }
    #![allow(unused)]
    fn main() {
    // When a temporary is promoted but not lifetime extended, its
    // place expression is still treated as transient.
    const C: () = { let _: &'static mut [u8] = &mut []; };
    }

    位置表达式是间接的,如果它是解引用表达式

    #![allow(unused)]
    fn main() {
    const C: () = { _ = &mut *(&mut 0); };
    }

    位置表达式是静态的,如果它是 static 项。

    #![allow(unused)]
    fn main() {
    #![allow(static_mut_refs)]
    const C: &u8 = unsafe { static mut S: u8 = 0; &mut S };
    }

    Note

    这些规则的一个令人惊讶的结果是我们允许这样做,

    #![allow(unused)]
    fn main() {
    const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // OK
    //                                    ~~~~~~~
    // Empty arrays are promoted even behind mutable borrows.
    }

    但我们禁止这种类似的代码:

    #![allow(unused)]
    fn main() {
    const C: &[u8] = &mut []; // ERROR
    //               ~~~~~~~
    //           Tail expression.
    }

    它们之间的区别在于,在第一个中,空数组被提升但其作用域不经历临时生命周期扩展,因此我们认为位置表达式是瞬态的(即使提升后该位置确实存活到程序末尾)。在第二个中,空数组临时的作用域确实经历了生命周期扩展,因此由于它是生命周期扩展临时的可变借用(因此借用非瞬态位置表达式)而被拒绝。

    此效果令人惊讶,因为在这种情况下,临时生命周期扩展导致比没有它时更少的代码能够编译。

    有关更多详细信息,请参阅 issue #143129

  • 解引用表达式

    #![allow(unused)]
    fn main() {
    use core::cell::UnsafeCell;
    const _: u8 = unsafe {
        let x: *mut u8 = &raw mut *&mut 0;
        //                        ^^^^^^^
        //             Dereference of mutable reference.
        *x = 1; // Dereference of mutable pointer.
        *(x as *const u8) // Dereference of constant pointer.
    };
    const _: u8 = unsafe {
        let x = &UnsafeCell::new(0);
        *x.get() = 1; // Mutation of interior mutable value.
        *x.get()
    };
    }
  • 强制转换表达式,以下情况除外:
    • 指针到地址的转换和
    • 函数指针到地址的转换。

Const 上下文

_Const 上下文_是以下之一:

数组类型长度表达式、数组重复长度表达式和 const 泛型参数在使用外部泛型参数方面受到限制:此类表达式必须是单个 const 泛型参数,或者是不引用任何泛型参数的表达式。

Const 函数

_const 函数_是从常量上下文中可以调用的函数。它使用 const 限定符定义,还包括元组结构体元组枚举变体构造函数。

Example

#![allow(unused)]
fn main() {
const fn square(x: i32) -> i32 { x * x }

const VALUE: i32 = square(12);
}

从常量上下文调用时,const 函数由编译器在编译时解释。解释发生在编译目标的环境中,而不是主机中。因此,如果您针对 32 位系统编译,usize32 位,无论您是在 64 位还是 32 位系统上构建。

从常量上下文外部调用 const 函数时,其行为与没有 const 限定符时相同。

const 函数的主体只能使用常量表达式

不允许 const 函数是异步的

const 函数的参数和返回类型的类型仅限于与常量上下文兼容的类型。

应用程序二进制接口 (ABI)

本节记录影响 crate 编译输出的 ABI 的功能。

有关指定导出函数的 ABI 的信息,请参阅*[外部函数]。有关指定链接外部库的 ABI 的信息,请参阅[外部块]*。

used 属性

*used [属性]*强制静态保留在输出目标文件(.o、.rlib 等,不包括最终二进制文件)中,即使它从未被 crate 中的任何其他项使用或引用。但是,链接器仍然可以自由地移除它。

Example

#![allow(unused)]
fn main() {
// lib.rs

// This is kept because of `#[used]`.
#[used]
static S1: u8 = 0;

// This is removable because it's unused.
#[allow(dead_code)]
static S2: u8 = 0;

// This is kept because it's publicly reachable.
pub static S3: u8 = 0;

// This is kept because it's referenced by a publicly
// reachable function.
static S4: u8 = 0;
#[unsafe(no_mangle)] pub fn f4() -> &'static u8 { &S4 }

// This is removable because it's referenced only by a
// private, unused (dead) function.
static S5: u8 = 0;
#[allow(dead_code)]
fn f5() -> &'static u8 { &S5 }
}
$ rustc -O --emit=obj --crate-type=rlib lib.rs
$ LC_ALL=C nm -C lib.o
0000000000000000 R lib::S1
0000000000000000 R lib::S3
0000000000000000 r lib::S4
0000000000000000 T f4

used 属性使用 MetaWord 语法。

used 属性只能应用于 [static 项]。

只有项上的第一次使用 used 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。

no_mangle 属性

no_mangle 属性可用于任何以禁用标准符号名称修饰。该项的符号将是该项名称的标识符。

此外,该项将从生成的库或目标文件中公开导出,类似于 used 属性

此属性是不安全的,因为未修饰的符号可能与同名的另一个符号(或知名符号)冲突,导致未定义行为。

#![allow(unused)]
fn main() {
#[unsafe(no_mangle)]
extern "C" fn foo() {}
}

2024 Edition differences

在 2024 版本之前,允许使用不带 unsafe 限定的 no_mangle 属性。

link_section 属性指定函数静态的内容将被放入的目标文件部分。

link_section 属性使用 MetaNameValueStr 语法指定部分名称。

#![allow(unused)]
fn main() {
#[cfg(target_os = "linux")] {
#[unsafe(no_mangle)]
#[unsafe(link_section = ".example_section")]
pub static VAR1: u32 = 1;
}
}

此属性是不安全的,因为它允许用户将数据和代码放入不期望它们的内存部分,例如将可变数据放入只读区域。

只有项上的第一次使用 link_section 才有效果。

Note

rustc 会对第一次之后的任何使用发出带有未来兼容性警告的 lint 警告。这可能在未来成为错误。

2024 Edition differences

在 2024 版本之前,允许使用不带 unsafe 限定的 link_section 属性。

export_name 属性

export_name 属性指定将在函数静态上导出的符号名称。

export_name 属性使用 MetaNameValueStr 语法指定符号名称。

#![allow(unused)]
fn main() {
#[unsafe(export_name = "exported_symbol_name")]
pub fn name_in_rust() { }
}

此属性是不安全的,因为具有自定义名称的符号可能与同名的另一个符号(或知名符号)冲突,导致未定义行为。

只有项上的第一次使用 export_name 才有效果。

Note

rustc 会对第一次之后的任何使用发出带有未来兼容性警告的 lint 警告。这可能在未来成为错误。

2024 Edition differences

在 2024 版本之前,允许使用不带 unsafe 限定的 export_name 属性。

Rust 运行时

本节记录定义 Rust 运行时某些方面的功能。

global_allocator 属性

*global_allocator 属性*选择[内存分配器]memory allocator

Example

#![allow(unused)]
fn main() {
use core::alloc::{GlobalAlloc, Layout};
use std::alloc::System;

struct MyAllocator;

unsafe impl GlobalAlloc for MyAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        unsafe { System.alloc(layout) }
    }
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        unsafe { System.dealloc(ptr, layout) }
    }
}

#[global_allocator]
static GLOBAL: MyAllocator = MyAllocator;
}

global_allocator 属性使用 MetaWord 语法。

global_allocator 属性只能应用于类型实现 GlobalAlloc trait 的静态项

global_allocator 属性只能在项上使用一次。

global_allocator 属性只能在 crate 图中使用一次。

global_allocator 属性从标准库 prelude 导出。

windows_subsystem 属性

*windows_subsystem 属性*在 Windows 目标上链接时设置子系统

Example

#![allow(unused)]
#![windows_subsystem = "windows"]
fn main() {
}

windows_subsystem 属性使用 MetaNameValueStr 语法。接受的值是 "console""windows"

windows_subsystem 属性只能应用于 crate 根。

只有第一次使用 windows_subsystem 才有效果。

Note

rustc 会对第一次之后的任何使用发出 lint 警告。这可能在未来成为错误。

windows_subsystem 属性在非 Windows 目标和非 bin crate 类型上被忽略。

"console" 子系统是默认值。如果控制台进程从现有控制台运行,则它将附加到该控制台;否则将创建新的控制台窗口。

"windows" 子系统将与任何现有控制台分离运行。

Note

"windows" 子系统通常被不希望在启动时显示控制台窗口的 GUI 应用程序使用。

附录

语法摘要

以下是语法规则产生式的摘要。有关此语法的语法详细信息,请参阅 notation.grammar.syntax

{{ grammar-summary }}

语法索引

本附录提供了词法单元和常见形式的索引,以及指向这些元素定义位置的链接。

关键字

关键字用途
_通配符模式推断 const推断类型占位生命周期常量项extern crateuse 声明解构赋值
abstract保留关键字
asextern crateuse 声明类型转换表达式限定路径
async异步函数异步块异步闭包
awaitawait 表达式
become保留关键字
box保留关键字
breakbreak 表达式
constconst 函数const 项const 泛型const 块原始借用运算符原始指针类型const 汇编操作数
continuecontinue 表达式
crateextern crate可见性路径
do保留关键字
dyntrait 对象
elselet 语句if 表达式
enum枚举
externextern crateextern 函数限定符外部块extern 函数指针类型
false布尔类型布尔表达式配置谓词
final保留关键字
fn函数函数指针类型
fortrait 实现迭代器循环高阶 trait 约束
gen保留关键字
ifif 表达式match 守卫
impl固有实现trait 实现impl trait 类型匿名类型参数
in可见性迭代器循环汇编操作数
letlet 语句if let 模式
loop无限循环
macro_rules通过示例定义的宏
macro保留关键字
matchmatch 表达式
mod模块
move闭包表达式异步块
mut借用表达式标识符模式引用模式结构体模式引用类型原始指针类型self 参数静态项
override保留关键字
priv保留关键字
pub可见性
raw借用表达式原始汇编
ref标识符模式结构体模式
returnreturn 表达式
safe外部块函数外部块静态
selfextern crateself 参数可见性self 路径
SelfSelf 类型路径use 约束
static静态项'static 生命周期
struct结构体
supersuper 路径可见性
traittrait 项
true布尔类型布尔表达式配置谓词
try保留关键字
type类型别名
typeof保留关键字
union联合体项
unsafeunsafe 块unsafe 属性unsafe 模块unsafe 函数unsafe 外部块unsafe 外部函数unsafe 外部静态unsafe traitunsafe trait 实现
unsized保留关键字
useuse 项use 约束
virtual保留关键字
wherewhere 子句
while谓词循环
yield保留关键字

运算符和标点符号

符号名称用途
+加号加法、[trait 约束]、[宏 Kleene 匹配器]
-减号减法取反
*星号乘法解引用、[原始指针]、[宏 Kleene 匹配器]、[glob 导入]
/斜杠除法
%百分号取余
^脱字符按位和逻辑异或
!按位和逻辑非、[宏调用]、内部属性、[永不类型]、[否定实现]
&按位和逻辑与借用、[引用]、[引用模式]
|按位和逻辑或、[闭包]、[或模式]、if letwhile let
&&与与惰性与借用、[引用]、[引用模式]
||或或惰性或、[闭包]
<<左移左移嵌套泛型
>>右移右移嵌套泛型
+=加等加法赋值
-=减等减法赋值
*=乘等乘法赋值
/=除等除法赋值
%=模等取余赋值
^=异或等按位异或赋值
&=与等按位与赋值
|=或等按位或赋值
<<=左移等左移赋值
>>=右移等右移赋值嵌套泛型
=等号[赋值]、[let 语句]、[属性]、各种类型定义
==等等等于
!=不等不等于
>大于大于、[泛型]、[路径]、[use 约束]
<小于小于、[泛型]、[路径]、[use 约束]
>=大于等于大于等于、[泛型]
<=小于等于小于等于
@At子模式绑定
.字段访问、[元组索引]、[方法调用]、[await 表达式]
..点点范围表达式、[结构体表达式]、休息模式范围模式结构体模式
...点点点可变参数函数范围模式
..=点点等包含范围表达式范围模式
,逗号各种分隔符
;分号各种项和语句的终止符、[数组表达式]、[数组类型]
:冒号各种分隔符
::路径分隔符路径分隔符
->右箭头[函数]、[闭包]、[函数指针类型]
=>胖箭头match 分支、[宏]
<-左箭头左箭头符号自 Rust 1.0 之前就未使用,但仍被视为单个词法单元。
#井号[属性]、[原始字符串字面量]、[原始字节字符串字面量]、[原始 C 字符串字面量]
$美元符号[宏]
?问号try 传播表达式、[放宽的 trait 约束]、[宏 Kleene 匹配器]
~波浪号波浪号运算符自 Rust 1.0 之前就未使用,但其词法单元仍可使用。

注释

其他词法单元

词法单元用途
ident标识符
r#ident原始标识符
'ident生命周期和循环标签
'r#ident原始生命周期和循环标签
…u8, …i32, …f64, …usize, …数字字面量
"…"字符串字面量
r"…", r#"…"#, r##"…"##, …原始字符串字面量
b"…"字节字符串字面量
br"…", br#"…"#, br##"…"##, …原始字节字符串字面量
'…'字符字面量
b'…'字节字面量
c"…"C 字符串字面量
cr"…", cr#"…"#, cr##"…"##, …原始 C 字符串字面量

语法用途
ident!(…)
ident! {…}
ident![…]
宏调用
$ident宏元变量
$ident:kind宏匹配器片段说明符
$(…)…宏重复

属性

语法用途
#[meta]外部属性
#![meta]内部属性

表达式

表达式用途
|…| expr
|…| -> Type { … }
闭包
ident::…路径
::crate_name::…显式 crate 路径
crate::…crate 相对路径
self::…模块相对路径
super::…父模块路径
Type::…
<Type as Trait>::ident
关联项
<Type>::…限定路径,可用于没有名称的类型,如 <&T>::…<[T]>::… 等。
Trait::method(…)
Type::method(…)
<Type as Trait>::method(…)
消除歧义的方法调用
method::<…>(…)
path::<…>
泛型参数,即 turbofish
()单元
(expr)括号表达式
(expr,)单元素元组表达式
(expr, …)元组表达式
expr(expr, …)调用表达式
expr.0, expr.1, …元组索引表达式
expr.ident字段访问表达式
{…}块表达式
Type {…}结构体表达式
Type(…)元组结构体构造函数
[…]数组表达式
[expr; len]重复数组表达式
expr[..], expr[a..], expr[..b], expr[a..b], expr[a..=b], expr[..=b]数组和切片索引表达式
if expr {…} else {…}if 表达式
match expr { pattern => {…} }match 表达式
loop {…}无限循环表达式
while expr {…}谓词循环表达式
for pattern in expr {…}迭代器循环
&expr
&mut expr
借用表达式
&raw const expr
&raw mut expr
原始借用表达式
*expr解引用表达式
expr?try 传播表达式
-expr取反表达式
!expr按位和逻辑非表达式
expr as Type类型转换表达式

是 crate 的组成部分。

用途
mod ident;
mod ident {…}
模块
use path;use 声明
fn ident(…) {…}函数
type Type = Type;类型别名
struct ident {…}结构体
enum ident {…}枚举
union ident {…}联合体
trait ident {…}trait
impl Type {…}
impl Type for Trait {…}
实现
const ident = expr;常量项
static ident = expr;静态项
extern "C" {…}外部块
fn ident<…>(…) …
struct ident<…> {…}
enum ident<…> {…}
impl<…> Type<…> {…}
泛型定义

类型表达式

类型表达式用于引用类型。

类型用途
bool, u8, f64, str, …原始类型
for<…>高阶 trait 约束
T: TraitA + TraitBtrait 约束
T: 'a + 'b生命周期约束
T: TraitA + 'atrait 和生命周期约束
T: ?Sized放宽的 trait 约束
[Type; len]数组类型
(Type, …)元组类型
[Type]切片类型
(Type)括号类型
impl Traitimpl trait 类型匿名类型参数
dyn Traittrait 对象类型
ident
ident::…
类型路径(可以引用结构体枚举联合体类型别名trait泛型 等)
Type<…>
Trait<…>
泛型参数(例如 Vec<u8>
Trait<ident = Type>关联类型绑定(例如 Iterator<Item = T>
Trait<ident: …>关联类型约束(例如 Iterator<Item: Send>
&Type
&mut Type
引用类型
*mut Type
*const Type
原始指针类型
fn(…) -> Type函数指针类型
_推断类型推断 const
'_占位生命周期
!永不类型

模式

模式用于匹配值。

模式用途
"foo", 'a', 123, 2.4, …字面量模式
ident标识符模式
_通配符模式
..休息模式
a.., ..b, a..b, a..=b, ..=b范围模式
&pattern
&mut pattern
引用模式
path {…}结构体模式
path(…)元组结构体模式
(pattern, …)元组模式
(pattern)分组模式
[pattern, …]切片模式
CONST, Enum::Variant, …路径模式

附录:宏跟随集歧义形式规范

本页记录了通过示例定义的宏跟随规则的形式规范。它们最初在 RFC 550 中指定,本文的大部分内容都是从那里复制的,并在后续 RFC 中进行了扩展。

定义和约定

  • macro:在源代码中可以作为 foo!(...) 调用的任何内容。
  • MBE:通过示例定义的宏,由 macro_rules 定义的宏。
  • matchermacro_rules 调用中规则的左侧,或其子部分。
  • macro parser:Rust 解析器中使用从所有匹配器派生的语法解析输入的代码部分。
  • fragment:给定匹配器将接受(或“匹配“)的 Rust 语法类别。
  • repetition:遵循正则重复模式的片段
  • NT:非终结符,可以出现在匹配器中的各种“元变量“或重复匹配器,在 MBE 语法中以 $ 字符开头指定。
  • simple NT:“元变量“非终结符(下面进一步讨论)。
  • complex NT:重复匹配非终结符,通过重复运算符(*+?)指定。
  • token:匹配器的原子元素;即标识符、运算符、开/闭分隔符、简单 NT。
  • token tree:由词法单元(叶子)、复杂 NT 和词法单元树的有限序列形成的树结构。
  • delimiter token:用于分隔一个片段结束和下一个片段开始的词法单元。
  • separator token:复杂 NT 中的可选分隔词法单元,用于分隔匹配重复中的每对元素。
  • separated complex NT:具有自己分隔词法单元的复杂 NT。
  • delimited sequence:在序列的开头和结尾具有适当开/闭分隔符的词法单元树序列。
  • empty fragment:分隔音法单元的不可见 Rust 语法类别,即空白,或(在某些词法上下文中)空词法单元序列。
  • fragment specifier:简单 NT 中指定 NT 接受哪个片段的标识符。
  • language:上下文无关语言。

示例:

#![allow(unused)]
fn main() {
macro_rules! i_am_an_mbe {
    (start $foo:expr $($i:ident),* end) => ($foo)
}
}

(start $foo:expr $($i:ident),* end) 是一个匹配器。整个匹配器是一个分隔序列(开/闭分隔符为 ()),$foo$i 是以 exprident 作为各自片段说明符的简单 NT。

$(i:ident),* 也是一个 NT;它是一个匹配逗号分隔的标识符重复的复杂 NT。, 是复杂 NT 的分隔词法单元;它出现在匹配片段的每对元素之间(如果有的话)。

复杂 NT 的另一个示例是 $(hi $e:expr ;)+,它匹配形式为 hi <expr>; hi <expr>; ... 的任何片段,其中 hi <expr>; 至少出现一次。请注意,此复杂 NT 没有专用的分隔词法单元。

(请注意,Rust 的解析器确保分隔序列始终以适当的词法单元树结构嵌套和正确的开/闭分隔符匹配出现。)

我们倾向于使用变量 “M” 表示匹配器,变量 “t” 和 “u” 表示任意单个词法单元,变量 “tt” 和 “uu” 表示任意词法单元树。(使用 “tt” 确实与其作为片段说明符的额外角色存在潜在歧义;但从上下文可以清楚地看出所指的解释。)

“SEP” 将遍历分隔词法单元,“OP” 遍历重复运算符 *+?,“OPEN”/“CLOSE” 遍历围绕分隔序列的匹配词法单元对(例如 [])。

希腊字母 “α” “β” “γ” “δ” 表示可能为空的词法单元树序列。(然而,希腊字母 “ε”(epsilon)在演示中具有特殊角色,不表示词法单元树序列。)

  • 这种希腊字母约定通常仅在序列的存在是技术细节时使用;特别是,当我们希望强调我们正在操作词法单元树序列时,我们将使用符号 “tt …” 表示序列,而不是希腊字母。

请注意,匹配器只是一个词法单元树。如上所述,“简单 NT” 是元变量 NT;因此它不是重复。例如,$foo:ty 是简单 NT,但 $($foo:ty)+ 是复杂 NT。

另请注意,在此形式主义的上下文中,术语“词法单元“通常包括简单 NT。

最后,读者记住以下内容很有用:根据此形式主义的定义,没有简单 NT 匹配空片段,同样没有词法单元匹配 Rust 语法的空片段。(因此,唯一可以匹配空片段的 NT 是复杂 NT。)这实际上并不正确,因为 vis 匹配器可以匹配空片段。因此,出于形式主义的目的,我们将 $v:vis 视为实际上是 $($v:vis)?,要求匹配器匹配空片段。

匹配器不变量

要有效,匹配器必须满足以下三个不变量。FIRST 和 FOLLOW 的定义稍后描述。

  1. 对于匹配器 M 中任意两个连续的词法单元树序列(即 M = ... tt uu ...),其中 uu ... 非空,我们必须有 FOLLOW(... tt) ∪ {ε} ⊇ FIRST(uu ...)。
  2. 对于匹配器中的分隔复杂 NT,M = ... $(tt ...) SEP OP ...,我们必须有 SEP ∈ FOLLOW(tt ...)。
  3. 对于匹配器中的非分隔复杂 NT,M = ... $(tt ...) OP ...,如果 OP = *+,我们必须有 FOLLOW(tt ...) ⊇ FIRST(tt ...)。

第一个不变量说明匹配器之后的实际词法单元(如果有的话)必须在预定的跟随集中的某处。这确保了合法的宏定义将继续分配相同的决定,即 ... tt 在哪里结束,uu ... 在哪里开始,即使新的语法形式被添加到语言中。

第二个不变量说明分隔复杂 NT 必须使用一个分隔词法单元,该分隔词法单元是 NT 内部内容的预定跟随集的一部分。这确保了合法的宏定义将继续将输入片段解析为相同的 tt ... 分隔序列,即使新的语法形式被添加到语言中。

第三个不变量说明当我们有一个复杂 NT 可以匹配两个或多个相同内容的副本且中间没有分隔时,根据第一个不变量,它们必须允许彼此相邻放置。此不变量还要求它们非空,这消除了可能的歧义。

注意:由于历史疏忽和对该行为的显著依赖,第三个不变量目前未强制执行。目前尚未决定如何处理此问题。不尊重此行为的宏在 Rust 的未来版本中可能变得无效。请参阅跟踪问题

FIRST 和 FOLLOW,非正式地

给定的匹配器 M 映射到三个集合:FIRST(M)、LAST(M) 和 FOLLOW(M)。

三个集合中的每一个都由词法单元组成。FIRST(M) 和 LAST(M) 还可以包含一个特殊的非词法单元元素 ε(“epsilon”),表示 M 可以匹配空片段。(但 FOLLOW(M) 始终只是词法单元的集合。)

非正式地:

  • FIRST(M):收集将片段与 M 匹配时可能首先使用的词法单元。
  • LAST(M):收集将片段与 M 匹配时可能最后使用的词法单元。
  • FOLLOW(M):允许紧跟在 M 匹配的某个片段之后的词法单元集合。

    换句话说:t ∈ FOLLOW(M) 当且仅当存在(可能为空的)词法单元序列 α、β、γ、δ,其中:

    • M 匹配 β,

    • t 匹配 γ,以及

    • 连接 α β γ δ 是可解析的 Rust 程序。

我们使用缩写 ANYTOKEN 表示所有词法单元(包括简单 NT)的集合。例如,如果任何词法单元在匹配器 M 之后是合法的,则 FOLLOW(M) = ANYTOKEN。

(为了回顾对上述非正式描述的理解,读者此时可能想跳到 FIRST/LAST 的示例,然后再阅读它们的形式定义。)

FIRST、LAST

以下是 FIRST 和 LAST 的形式归纳定义。

“A ∪ B” 表示集合并集,“A ∩ B” 表示集合交集,“A \ B” 表示集合差集(即 A 中不存在于 B 中的所有元素)。

FIRST

FIRST(M) 通过对序列 M 及其第一个词法单元树(如果有的话)的结构进行案例分析定义:

  • 如果 M 是空序列,则 FIRST(M) = { ε },
  • 如果 M 以词法单元 t 开头,则 FIRST(M) = { t },

    (注意:这涵盖了 M 以分隔音法单元树序列开头的情况,M = OPEN tt ... CLOSE ...,在这种情况下 t = OPEN,因此 FIRST(M) = { OPEN }。)

    (注意:这严重依赖于没有简单 NT 匹配空片段的属性。)

  • 否则,M 是以复杂 NT 开头的词法单元树序列:M = $( tt ... ) OP α,或 M = $( tt ... ) SEP OP α,(其中 α 是匹配器其余部分的(可能为空的)词法单元树序列)。

    • 如果 SEP 存在且 ε ∈ FIRST(tt ...),则令 SEP_SET(M) = { SEP };否则 SEP_SET(M) = {}。
  • 如果 OP = *?,则令 ALPHA_SET(M) = FIRST(α),如果 OP = +,则令 ALPHA_SET(M) = {}。

  • FIRST(M) = (FIRST(tt ...) \ {ε}) ∪ SEP_SET(M) ∪ ALPHA_SET(M)。

复杂 NT 的定义需要一些理由。SEP_SET(M) 定义了分隔符可能是 M 的有效第一个词法单元的可能性,这在定义了分隔符且重复片段可能为空时发生。ALPHA_SET(M) 定义了复杂 NT 可能为空的可能性,这意味着 M 的有效第一个词法单元是以下词法单元树序列 α 的词法单元。当使用 *? 时发生这种情况,此时可能有零次重复。理论上,如果 + 与可能为空的重复片段一起使用,也可能发生这种情况,但这被第三个不变量禁止。

从那里开始,显然 FIRST(M) 可以包含来自 SEP_SET(M) 或 ALPHA_SET(M) 的任何词法单元,如果复杂 NT 匹配非空,则 FIRST(tt ...) 开始的任何词法单元也可以工作。要考虑的最后一部分是 ε。SEP_SET(M) 和 FIRST(tt ...) \ {ε} 不能包含 ε,但 ALPHA_SET(M) 可以。因此,此定义允许 M 接受 ε 当且仅当 ε ∈ ALPHA_SET(M) 时。这是正确的,因为对于 M 在复杂 NT 情况下接受 ε,复杂 NT 和 α 都必须接受它。如果 OP = +,意味着复杂 NT 不能为空,则根据定义 ε ∉ ALPHA_SET(M)。否则,复杂 NT 可以接受零次重复,然后 ALPHA_SET(M) = FOLLOW(α)。因此此定义关于 \varepsilon 也是正确的。

LAST

LAST(M),通过对 M 本身(词法单元树序列)进行案例分析定义:

  • 如果 M 是空序列,则 LAST(M) = { ε }
  • 如果 M 是单元素词法单元 t,则 LAST(M) = { t }
  • 如果 M 是重复零次或多次的单元素复杂 NT,M = $( tt ... ) *,或 M = $( tt ... ) SEP *

    • 如果 SEP 存在,则令 sep_set = { SEP };否则 sep_set = {}。

    • 如果 ε ∈ LAST(tt ...),则 LAST(M) = LAST(tt ...) ∪ sep_set

    • 否则,序列 tt ... 必须非空;LAST(M) = LAST(tt ...) ∪ {ε}。

  • 如果 M 是重复一次或多次的单元素复杂 NT,M = $( tt ... ) +,或 M = $( tt ... ) SEP +

    • 如果 SEP 存在,则令 sep_set = { SEP };否则 sep_set = {}。

    • 如果 ε ∈ LAST(tt ...),则 LAST(M) = LAST(tt ...) ∪ sep_set

    • 否则,序列 tt ... 必须非空;LAST(M) = LAST(tt ...

  • 如果 M 是重复零次或一次的单元素复杂 NT,M = $( tt ...) ?,则 LAST(M) = LAST(tt ...) ∪ {ε}。
  • 如果 M 是分隔音法单元树序列 OPEN tt ... CLOSE,则 LAST(M) = { CLOSE }。
  • 如果 M 是非空的词法单元树序列 tt uu ...

    • 如果 ε ∈ LAST(uu ...),则 LAST(M) = LAST(tt) ∪ (LAST(uu ...) \ { ε })。

    • 否则,序列 uu ... 必须非空;则 LAST(M) = LAST(uu ...)。

FIRST 和 LAST 的示例

以下是 FIRST 和 LAST 的一些示例。(特别注意 ε 特殊元素是如何根据输入部分之间的交互引入和消除的。)

我们的第一个示例以树结构呈现,以详细说明匹配器的分析是如何组合的。(一些较简单的子树已被省略。)

INPUT:  $(  $d:ident   $e:expr   );*    $( $( h )* );*    $( f ; )+   g
            ~~~~~~~~   ~~~~~~~                ~
                |         |                   |
FIRST:   { $d:ident }  { $e:expr }          { h }


INPUT:  $(  $d:ident   $e:expr   );*    $( $( h )* );*    $( f ; )+
            ~~~~~~~~~~~~~~~~~~             ~~~~~~~           ~~~
                        |                      |               |
FIRST:          { $d:ident }               { h, ε }         { f }

INPUT:  $(  $d:ident   $e:expr   );*    $( $( h )* );*    $( f ; )+   g
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~    ~~~~~~~~~~~~~~    ~~~~~~~~~   ~
                        |                       |              |       |
FIRST:        { $d:ident, ε }            {  h, ε, ;  }      { f }   { g }


INPUT:  $(  $d:ident   $e:expr   );*    $( $( h )* );*    $( f ; )+   g
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                        |
FIRST:                       { $d:ident, h, ;,  f }

因此:

  • FIRST($($d:ident $e:expr );* $( $(h)* );* $( f ;)+ g) = { $d:ident, h, ;, f }

但请注意:

  • FIRST($($d:ident $e:expr );* $( $(h)* );* $($( f ;)+ g)*) = { $d:ident, h, ;, f, ε }

以下是类似的示例,但现在是 LAST。

  • LAST($d:ident $e:expr) = { $e:expr }
  • LAST($( $d:ident $e:expr );*) = { $e:expr, ε }
  • LAST($( $d:ident $e:expr );* $(h)*) = { $e:expr, ε, h }
  • LAST($( $d:ident $e:expr );* $(h)* $( f ;)+) = { ; }
  • LAST($( $d:ident $e:expr );* $(h)* $( f ;)+ g) = { g }

FOLLOW(M)

最后,FOLLOW(M) 的定义如下构建。pat、expr 等表示具有给定片段说明符的简单非终结符。

  • FOLLOW(pat) = {=>, ,, =, |, if, in}`。
  • FOLLOW(expr) = FOLLOW(expr_2021) = FOLLOW(stmt) = {=>, ,, ;}`。
  • FOLLOW(ty) = FOLLOW(path) = {{, [, ,, =>, :, =, >, >>, ;, |, as, where, block nonterminals}。
  • FOLLOW(vis) = {,priv 外的任何关键字或标识符;可以开始类型的任何词法单元;ident、ty 和 path 非终结符}。
  • FOLLOW(t) = ANYTOKEN 对于任何其他简单词法单元,包括 block、ident、tt、item、lifetime、literal 和 meta 简单非终结符,以及所有终结符。
  • FOLLOW(M),对于任何其他 M,定义为当 t 遍历 (LAST(M) \ {ε}) 时 FOLLOW(t) 的交集。

截至撰写本文时,可以开始类型的词法单元是 {(, [, !, *, &, &&, ?, lifetimes, >, >>, ::, any non-keyword identifier, super, self, Self, extern, crate, $crate, _, for, impl, fn, unsafe, typeof, dyn},尽管此列表可能不完整,因为人们不会总是在添加新内容时记得更新附录。

复杂 M 的 FOLLOW 示例:

  • FOLLOW($( $d:ident $e:expr )*) = FOLLOW($e:expr)
  • FOLLOW($( $d:ident $e:expr )* $(;)*) = FOLLOW($e:expr) ∩ ANYTOKEN = FOLLOW($e:expr)
  • FOLLOW($( $d:ident $e:expr )* $(;)* $( f |)+) = ANYTOKEN

有效和无效匹配器的示例

借助上述规范,我们可以提出为什么特定匹配器合法而其他匹配器不合法的论据。

  • ($ty:ty < foo ,):非法,因为 FIRST(< foo ,) = { < } ⊈ FOLLOW(ty)

  • ($ty:ty , foo <):合法,因为 FIRST(, foo <) = { , } ⊆ FOLLOW(ty)。

  • ($pa:pat $pb:pat $ty:ty ,):非法,因为 FIRST($pb:pat $ty:ty ,) = { $pb:pat } ⊈ FOLLOW(pat),且 FIRST($ty:ty ,) = { $ty:ty } ⊈ FOLLOW(pat)。

  • ( $($a:tt $b:tt)* ; ):合法,因为 FIRST($b:tt) = { $b:tt } ⊆ FOLLOW(tt) = ANYTOKEN,FIRST(;) = { ; } 也是如此。

  • ( $($t:tt),* , $(t:tt),* ):合法,(尽管任何实际使用此宏的尝试都会在展开期间发出本地歧义错误)。

  • ($ty:ty $(; not sep)* -):非法,因为 FIRST($(; not sep)* -) = { ;, - } 不在 FOLLOW(ty)中。

  • ($($ty:ty)-+):非法,因为分隔符 - 不在 FOLLOW(ty)中。

  • ($($e:expr)*):非法,因为 expr NT 不在 FOLLOW(expr NT) 中。

影响

Rust 不是一种特别原创的语言,其设计元素来自广泛的来源。以下列出了其中一些(包括后来被移除的元素):

  • SML、OCaml:代数数据类型、模式匹配、类型推断、分号语句分隔
  • C++:引用、RAII、智能指针、移动语义、单态化、内存模型
  • ML Kit、Cyclone:基于区域的内存管理
  • Haskell (GHC):类型类、类型族
  • Newsqueak、Alef、Limbo:通道、并发
  • Erlang:消息传递、线程失败、链接线程失败轻量级并发
  • Swift:可选绑定
  • Scheme:卫生宏
  • C#:属性
  • Ruby:闭包语法、块语法
  • NIL、Hermes:类型状态
  • Unicode Annex #31:标识符和模式语法

测试摘要

以下是链接到参考文档中各个规则标识符的测试总数摘要。

{{summary-table}}

术语表

抽象语法树

“抽象语法树“或“AST“是编译器编译程序时程序结构的中间表示。

对齐

值的对齐指定值首选从哪些地址开始。始终是 2 的幂。对值的引用必须是对齐的。更多信息

应用程序二进制接口 (ABI)

应用程序二进制接口 (ABI) 定义了编译后的代码如何与其他编译后的代码交互。使用 [extern 块]和 extern fn 时,ABI 字符串影响:

  • 调用约定:如何传递函数参数、返回值(例如在寄存器中还是在栈上),以及谁负责清理栈。
  • 展开:是否允许栈展开。例如,"C-unwind" ABI 允许跨 FFI 边界展开,而 "C" ABI 不允许。

元数

元数指的是函数或运算符接受的参数数量。例如,f(2, 3)g(4, 6) 的元数为 2,而 h(8, 2, 6) 的元数为 3。! 运算符的元数为 1。

数组

数组,有时也称为固定大小数组或内联数组,是描述元素集合的值,每个元素由程序在运行时可计算的索引选择。它占据连续的内存区域。

关联项

关联项是与另一个项关联的项。关联项在实现中定义,在 trait 中声明。只有函数、常量和类型别名可以被关联。与自由项对比。

全面实现

任何类型出现未覆盖的实现。impl<T> Foo for Timpl<T> Bar<T> for Timpl<T> Bar<Vec<T>> for Timpl<T> Bar<T> for Vec<T> 被视为全面实现。然而,impl<T> Bar<Vec<T>> for Vec<T> 不是全面实现,因为此 impl 中出现的所有 T 实例都被 Vec 覆盖。

约束

约束是对类型或 trait 的限制。例如,如果对函数接受的参数设置了约束,则传递给该函数的类型必须遵守该约束。

组合子

组合子是高阶函数,只应用函数和先前定义的组合子来从其参数提供结果。它们可用于以模块化方式管理控制流。

Crate

Crate 是编译和链接的单元。有不同类型的 crate,例如库或可执行文件。crate 可以链接和引用其他库 crate,称为外部 crate。crate 具有自包含的模块树,从称为 crate 根的未命名根模块开始。通过在 crate 根中将标记为公开,可以使它们对其他 crate 可见,包括通过公开模块的路径更多信息

分派

分派是当涉及多态性时确定实际运行哪个特定版本代码的机制。两种主要的分派形式是静态分派和动态分派。Rust 通过使用 trait 对象支持动态分派。

动态大小类型

动态大小类型 (DST) 是没有静态已知大小或对齐的类型。

实体

实体是一种语言构造,可以在源程序中以某种方式引用,通常通过路径。实体包括类型泛型参数变量绑定循环标签生命周期字段属性lints

表达式

表达式是值、常量、变量、运算符和函数的组合,求值为单个值,有或没有副作用。

例如,2 + (3 * 4) 是一个返回值 14 的表达式。

自由项

不是实现成员的,例如自由函数自由常量。与关联项对比。

基础 trait

基础 trait 是为其添加现有类型的实现是破坏性更改的 trait。Fn trait 和 Sized 是基础的。

基础类型构造器

基础类型构造器是在其上实现全面实现是破坏性更改的类型。&&mutBoxPin 是基础的。

每当类型 T 被视为本地时,&T&mut TBox<T>Pin<T> 也被视为本地。基础类型构造器不能覆盖其他类型。每当使用术语“覆盖类型“时,&T&mut TBox<T>Pin<T> 中的 T 不被视为覆盖。

已居住

如果类型有构造函数,因此可以被实例化,则该类型是已居住的。已居住类型不是“空的“,因为可以有该类型的值。与未居住相反。

固有实现

应用于具名类型而不是 trait 类型对的实现更多信息

固有方法

固有实现中定义的方法,而不是在 trait 实现中。

已初始化

如果变量已被赋值且之后未被移出,则该变量是已初始化的。所有其他内存位置都被假定为未初始化的。只有 unsafe Rust 可以在不初始化的情况下创建内存位置。

本地 trait

在当前 crate 中定义的 trait。trait 定义是否本地与应用的类型参数无关。给定 trait Foo<T, U>Foo 始终是本地的,无论 TU 替换为什么类型。

本地类型

在当前 crate 中定义的 structenumunion。这不受应用的类型参数影响。struct Foo 被视为本地,但 Vec<Foo> 不是。LocalType<ForeignType> 是本地的。类型别名不影响本地性。

模块

模块是零个或多个的容器。模块组织成树状结构,从根处称为 crate 根或根模块的未命名模块开始。路径可用于引用其他模块中的项,这可能受到可见性规则的限制。更多信息

名称

名称是引用实体标识符生命周期或循环标签名称绑定是实体声明引入与该实体关联的标识符或标签时发生的情况。路径、标识符和标签用于引用实体。

名称解析

名称解析是将路径标识符标签绑定到实体声明的编译时过程。

命名空间

命名空间是根据名称引用的实体类型对声明的名称进行的逻辑分组。命名空间允许一个命名空间中的名称出现与另一个命名空间中的同名名称不冲突。

在命名空间内,名称按层次结构组织,其中层次结构的每一级都有自己的命名实体集合。

具名类型

可以直接通过路径引用的类型。具体来说是枚举结构体联合体trait 对象类型

Dyn 兼容 trait

可以在 trait 对象类型dyn Trait)中使用的 Trait。只有遵循特定规则的 trait 才是dyn 兼容的

这些以前称为对象安全 trait。

路径

路径是一个或多个路径段的序列,用于引用当前作用域或命名空间层次结构其他级别的实体

Prelude

Prelude 或 Rust Prelude 是一小部分项(主要是 trait)的集合,被导入到每个 crate 的每个模块中。prelude 中的 trait 是普遍存在的。

作用域

作用域是可以用该名称引用命名实体的源文本区域。

审查者

审查者是在 match 表达式和类似模式匹配构造中匹配的表达式。例如,在 match x { A => 1, B => 2 } 中,表达式 x 是审查者。

大小

值的大小有两个定义。

第一个是存储该值必须分配多少内存。

第二个是具有该项类型的数组中连续元素之间的字节偏移量。

它是对齐的倍数,包括零。大小可能因编译器版本(随着新优化的进行)和目标平台(类似于 usize 如何因平台而异)而变化。

更多信息

切片

切片是连续序列的动态大小视图,写为 [T]

它通常以其借用形式出现,可变或共享。共享切片类型是 &[T],而可变切片类型是 &mut [T],其中 T 表示元素类型。

语句

语句是编程语言中命令计算机执行操作的最小独立元素。

字符串字面量

字符串字面量是直接存储在最终二进制文件中的字符串,因此对 'static 持续时间有效。

其类型是 'static 持续时间借用的字符串切片,&'static str

字符串切片

字符串切片是 Rust 中最基本的字符串类型,写为 str。它通常以其借用形式出现,可变或共享。共享字符串切片类型是 &str,而可变字符串切片类型是 &mut str

字符串切片始终是有效的 UTF-8。

Trait

Trait 是用于描述类型必须提供的功能的语言项。它允许类型对其行为做出某些承诺。

泛型函数和泛型结构体可以使用 trait 来约束或限制它们接受的类型。

Turbofish

表达式中带有泛型参数的路径必须在开头的括号前加上 ::。与泛型的尖括号结合,这看起来像一条鱼 ::<>。因此,这种语法在口语中被称为 turbofish 语法。

示例:

#![allow(unused)]
fn main() {
let ok_num = Ok::<_, ()>(5);
let vec = [1, 2, 3].iter().map(|n| n * 2).collect::<Vec<_>>();
}

需要此 :: 前缀来消除逗号分隔列表中多次比较的泛型路径的歧义。有关没有前缀会产生歧义的示例,请参阅 turbofish 的堡垒

未覆盖类型

不作为另一个类型参数出现的类型。例如,T 是未覆盖的,但 Vec<T> 中的 T 是覆盖的。这仅与类型参数相关。

未定义行为

未指定的编译时或运行时行为。这可能导致但不限于:进程终止或损坏;不正确、错误或意外的计算;或特定于平台的结果。更多信息

未居住

如果类型没有构造函数,因此永远无法被实例化,则该类型是未居住的。未居住类型是“空的“,因为没有该类型的值。未居住类型的典型示例是永不类型 !,或没有变体的枚举 enum Never { }。与已居住相反。

零大小类型 (ZST)

如果类型的大小为 0,则该类型是零大小的 (ZST)。此类类型最多有一个可能的值。示例包括:

#![allow(unused)]
fn main() {
use core::mem::{size_of, size_of_val};
fn f() {}
struct S(u8);
enum E { V(u8) }
#[repr(C)]
struct C1 {}
#[repr(C)]
struct C2 {
    f1: (),
    f2: [(); 10],
    f3: [u8; 0],
    f4: C1,
}
#[repr(transparent)]
struct T1 {}
#[repr(transparent)]
struct T2 {
    f1: (),
    f2: [(); 10],
    f3: [u8; 0],
}
union U {
    f1: (),
    f2: [(); 10],
    f3: [u8; 0],
}
assert_eq!(0, size_of::<()>());
assert_eq!(0, size_of_val(&f));
assert_eq!(0, size_of_val(&S));
assert_eq!(0, size_of_val(&E::V));
assert_eq!(0, size_of::<C1>());
assert_eq!(0, size_of::<C2>());
assert_eq!(0, size_of::<T1>());
assert_eq!(0, size_of::<T2>());
assert_eq!(0, size_of::<[(); 10]>());
assert_eq!(0, size_of::<[u8; 0]>());
assert_eq!(0, size_of::<U>());
}