高级 The Rust Project Developers 著,rustwiki.org 中译 2026-09-13 14:59:08 · 0 阅读

第8章 属性

句法
InnerAttribute :
   # ! [ Attr ]

OuterAttribute :
   # [ Attr ]

Attr :
   SimplePath AttrInput?

AttrInput :
      DelimTokenTree
   | = LiteralExpression不带后缀

属性是一种通用的、格式自由的元数据(free-form metadatum),这种元数据会(被编译器/解释器)依据名称、约定、语言和编译器版本进行解释。(Rust 语言中的)属性是根据 ECMA-335 标准中的属性规范进行建模的,其语法来自 ECMA-334 (C#)。

*内部属性(Inner attributes)*以 #! 开头的方式编写,应用于它在其中声明的程序项。*外部属性(Outer attributes)*以不后跟感叹号的(!)的 # 开头的方式编写,应用于属性后面的内容。

属性由指向属性的路径和路径后跟的可选的带定界符的 token树(delimited token tree)(其解释由属性定义)组成。除了宏属性之外,其他属性的输入也允许使用等号(=)后跟文字表达式的格式。更多细节请参见下面的元项属性句法(meta item syntax)

属性可以分为以下几类:

属性可以应用于语言中的许多场景:

属性的一些例子:


#![allow(unused)]
fn main() {
// 应用于当前模块或 crate 的一般性元数据。
#![crate_type = "lib"]

// 标记为单元测试的函数
#[test]
fn test_foo() {
    /* ... */
}

// 一个条件编译模块
#[cfg(target_os = "linux")]
mod bar {
    /* ... */
}

// 用于静音 lint检查后报告的告警和错误提醒
#[allow(non_camel_case_types)]
type int8_t = i8;

// 适用于整个函数的内部属性
fn some_unused_variables() {
  #![allow(unused_variables)]

  let x = ();
  let y = ();
  let z = ();
}
}

元项/元程序项属性句法

“元项(meta item)”是遵循 Attr 产生式(见本章头部)的句法,Rust 的大多数内置属性(built-in attributes)都使用了此句法。它有以下文法格式:

句法
MetaItem :
      SimplePath
   | SimplePath = LiteralExpression不带后缀
   | SimplePath ( MetaSeq? )

MetaSeq :
   MetaItemInner ( , MetaItemInner )* ,?

MetaItemInner :
      MetaItem
   | LiteralExpression不带后缀

元项中的字面量表达式不能包含整型或浮点类型的后缀。

各种内置属性使用元项句法的不同子集来指定它们的输入。下面的文法规则展示了一些常用的使用形式:

句法
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 属性是活跃的。test属性在为测试所做的编译形式中是惰性的,在其他编译形式中是活跃的。宏属性是活跃的。所有其他属性都是惰性的。

外部工具属性

编译器可能允许和具体外部工具相关联的属性,但这些工具在编译和检查过程中必须存在并驻留在编译器提供的工具类预导入包下对应的命名空间中(才能让这些属性生效)。这种属性的(命名空间)路径的第一段是工具的名称,后跟一个或多个工具自己解释的附加段。

当工具在编译期不可用时,该工具的属性将被静默接受而不提示警告。当工具可用时,该工具负责处理和解释这些属性。

如果使用了 no_implicit_prelude属性,则外部工具属性不可用。


#![allow(unused)]
fn main() {
// 告诉rustfmt工具不要格式化以下元素。
#[rustfmt::skip]
struct S {
}

// 控制clippy工具的“圈复杂度(cyclomatic complexity)”极限值。
#[clippy::cyclomatic_complexity = "100"]
pub fn f() {}
}

注意: rustc 目前能识别的工具是 “clippy” 和 “rustfmt”。

内置属性的索引表

下面是所有内置属性的索引表:

  • 条件编译(Conditional compilation)
    • cfg — 控制条件编译。
    • cfg_attr — 选择性包含属性。
  • 测试(Testing)
    • test — 将函数标记为测试函数。
    • ignore — 禁止测试此函数。
    • should_panic — 表示测试应该产生 panic。
  • 派生(Derive)
  • 宏(Macros)
  • 诊断(Diagnostics)
  • ABI、链接(linking)、符号(symbol)、和 FFI
    • link — 指定要与外部(extern)块链接的本地库。
    • link_name — 指定外部(extern)块中的函数或静态项的符号(symbol)名。
    • no_link — 防止链接外部crate。
    • repr — 控制类型的布局。
    • crate_type — 指定 crate 的类别(库、可执行文件等)。
    • no_main — 禁止发布 main符号(symbol)。
    • export_name — 指定函数或静态项导出的符号(symbol)名。
    • link_section — 指定用于函数或静态项的对象文件的部分。
    • no_mangle — 禁用对符号(symbol)名编码。
    • used — 强制编译器在输出对象文件中保留静态项。
    • crate_name — 指定 crate名。
  • 代码生成(Code generation)
    • inline — 内联代码提示。
    • cold — 提示函数不太可能被调用。
    • no_builtins — 禁用某些内置函数。
    • target_feature — 配置特定于平台的代码生成。
    • track_caller - 将父调用位置传递给 std::panic::Location::caller()
  • 文档(Documentation)
  • 预导入包(Preludes)
  • 模块(Modules)
    • path — 指定模块的源文件名。
  • 极限值设置(Limits)
    • recursion_limit — 设置某些编译时操作的最大递归限制。
    • type_length_limit — 设置多态类型(polymorphic type)单态化过程中构造具体类型时所做的最大类型替换次数。
  • 运行时(Runtime)
  • 特性(Features)
    • feature — 用于启用非稳定的或实验性的编译器特性。参见 The Unstable Book 了解在 rustc 中实现的特性。
  • 类型系统(Type System)
    • non_exhaustive — 表明一个类型将来会添加更多的字段/变体。

测试类属性

以下属性用于指定函数来执行测试。在“测试(test)”模式下编译 crate 可以构建测试函数以及构建用于执行测试(函数)的测试套件(test harness)。启用测试模式还会启用 test条件编译选项

test属性

test属性标记一个用来执行测试的函数。这些函数只在测试模式下编译。测试函数必须是自由函数和单态函数,不能有参数,返回类型必须是以下类型之一:

  • ()
  • Result<(), E> where E: Error

注意:允许哪些返回类型是由暂未稳定的 Termination trait 决定的。

注意:测试模式是通过将 --test 参数选项传递给 rustc 或使用 cargo test 来启用的。

返回 () 的测试只要结束(terminate)且没有触发 panic 就会通过。返回 Result<(), E> 的测试只要它们返回 Ok(()) 就算通过。不结束的测试既不(计为)通过也不(计为)失败。


#![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()?; // 预期成功
    do_the_thing(&state)?;          // 预期成功
    Ok(())
}
}

ignore属性

test属性标注的(annotated with)函数也可以被 ignore属性标注。ignore属性告诉测试套件不要将该函数作为测试执行。但在测试模式下,这类函数仍然会被编译。

ignore属性可以选择使用 MetaNameValueStr元项属性句法来说明测试被忽略的原因。


#![allow(unused)]
fn main() {
#[test]
#[ignore = "not yet implemented"]
fn mytest() {
    // …
}
}

注意rustc 的测试套件支持使用 --include-ignored 参数选项来强制运行那些被忽略测试的函数。

should_panic属性

test属性标注并返回 () 的函数也可以被 should_panic属性标注。should_panic属性使测试函数只有在实际发生 panic 时才算通过。

should_panic属性可选输入一条出现在 panic消息中的字符串。如果在 panic消息中找不到该字符串,则测试将失败。可以使用 MetaNameValueStr元项属性句法或带有 expected字段的 MetaListNameValueStr元项属性句法来传递字符串。


#![allow(unused)]
fn main() {
#[test]
#[should_panic(expected = "值未匹配上")]
fn mytest() {
    assert_eq!(1, 2, "值未匹配上");
}
}

派生

derive属性允许为数据结构自动生成新的程序项。它使用 MetaListPaths元项属性句法(为程序项)指定一系列要实现的 trait 或指定要执行的派生宏的路径。

例如,下面的派生属性将为结构体 Foo 创建一个实现 PartialEq trait 和 Clone trait 的实现(impl item),类型参数 T 将被派生出的实现(impl)加上 PartialEq1 Clone 约束:


#![allow(unused)]
fn main() {
#[derive(PartialEq, Clone)]
struct Foo<T> {
    a: i32,
    b: T,
}
}

上面代码为 PartialEq 生成的实现(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
    }

    fn ne(&self, other: &Foo<T>) -> bool {
        self.a != other.a || self.b != other.b
    }
}
}

可以通过过程宏为自定义的 trait 实现自动派生(derive)功能。

automatically_derived属性

automatically_derived属性会被自动添加到由 derive属性为一些内置trait 自动派生的实现中。它对派生出的实现没有直接影响,但是工具和诊断lint 可以使用它来检测这些自动派生的实现。

1

原文后半句是:"and the type parameter T will be given the PartialEq or Clone constraints for the appropriate impl:",这里译者也搞不清楚为什么 PartialEqClone 之间用了"or",而不是"and"?这里译者就先采用直译。

诊断属性

以下属性用于在编译期间控制或生成诊断消息。

lint检查类属性

(译者注:lint在原文里有时当名词用,有时当动词用,本文统一翻译成名词,意思就是一种被命名的 lint检查模式)

lint检查(lint check)系统命名了一些潜在的不良编码模式,(这些被命名的 lint检查就是一个一个的lint,)例如编写了不可能执行到的代码,就被命名为 unreachable-code lint,编写未提供文档的代码就被命名为 missing_docs lint。allowwarndenyforbid 这些能调整代码检查级别的属性被称为 lint级别属性,它们使用 MetaListPaths元项属性句法来指定接受此级别的各种 lint。代码实体应用了这些带了具体 lint 列表的 lint级别属性,编译器或相关代码检查工具就可以结合这两层属性对这段代码执行相应的代码检查和检查报告。

对任何名为 C 的 lint 来说:

  • allow(C) 会压制对 C 的检查,那这样的违规行为就不会被报告,
  • warn(C) 警告违反 C 的,但继续编译。
  • deny(C) 遇到违反 C 的情况会触发编译器报错(signals an error),
  • forbid(C)deny(C) 相同,但同时会禁止以后再更改 lint级别,

注意:可以通过 rustc -W help 找到所有 rustc 支持的 lint,以及它们的默认设置,也可以在 rustc book 中找到相关文档。


#![allow(unused)]
fn main() {
pub mod m1 {
    // 这里忽略未提供文档的编码行为
    #[allow(missing_docs)]
    pub fn undocumented_one() -> i32 { 1 }

    // 未提供文档的编码行为在这里将触发编译器告警
    #[warn(missing_docs)]
    pub fn undocumented_too() -> i32 { 2 }

    // 未提供文档的编码行为在这里将触发编译器报错
    #[deny(missing_docs)]
    pub fn undocumented_end() -> i32 { 3 }
}
}

Lint属性可以覆盖上一个属性指定的级别,但该级别不能更改禁止性的 Lint。这里的上一个属性是指语法树中更高级别的属性,或者是同一实体上的前一个属性,按从左到右的源代码顺序列出。

此示例展示了如何使用 allowwarn 来打开和关闭一个特定的 lint检查:


#![allow(unused)]
fn main() {
#[warn(missing_docs)]
pub mod m2{
    #[allow(missing_docs)]
    pub mod nested {
        // 这里忽略未提供文档的编码行为
        pub fn undocumented_one() -> i32 { 1 }

        // 尽管上面允许,但未提供文档的编码行为在这里将触发编译器告警
        #[warn(missing_docs)]
        pub fn undocumented_two() -> i32 { 2 }
    }

    // 未提供文档的编码行为在这里将触发编译器告警
    pub fn undocumented_too() -> i32 { 3 }
}
}

此示例展示了如何使用 forbid 来禁止对该 lint 使用 allow


#![allow(unused)]
fn main() {
#[forbid(missing_docs)]
pub mod m3 {
    // 试图切换到 allow级别将触发编译器报错
    #[allow(missing_docs)]
    /// 返回 2。
    pub fn undocumented_too() -> i32 { 2 }
}
}

注意:rustc允许在命令行上设置 lint级别,也支持在setting caps中设置 lint报告中的caps。

lint分组

lint可以被组织成不同命名的组,以便相关lint的等级可以一起调整。使用命名组相当于给出该组中的 lint。


#![allow(unused)]
fn main() {
// 这允许 "unused"组下的所有lint。
#[allow(unused)]
// 这个禁止动作将覆盖前面 "unused"组中的 "unused_must_use" lint。
#[deny(unused_must_use)]
fn example() {
    // 这里不会生成告警,因为 "unused_variables" lint 在 "unused"组下
    let x = 1;
    // 这里会产生报错信息,因为 "unused_must_use" lint 被标记为"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() {}
// 这里的两个属性的前后顺序无所谓
#[deny(warnings)]
// unsafe_code lint 默认是 "allow" 的。
#[warn(unsafe_code)]
fn example_err() {
    // 这里会编译报错,因为 `unsafe_code`告警的 lint 被提升为 "deny"级别了。
    unsafe { an_unsafe_fn() } // ERROR: usage of `unsafe` block
}
}

Tool lint attributes

工具类lint属性

可以为 allowwarndenyforbid 这些调整代码检查级别的 lint属性添加/输入基于特定工具的 lint。注意该工具在当前作用域内可用才会起效。

工具类lint 只有在相关工具处于活动状态时才会做相应的代码模式检查。如果一个 lint级别属性,如 allow,引用了一个不存在的工具类lint,编译器将不会去警告不存在该 lint类,只有使用了该工具(,才会报告该 lint类不存在)。

在其他方面,这些工具类lint 就跟像常规的 lint 一样:

// 将 clippy 的整个 `pedantic` lint组设置为告警级别
#![warn(clippy::pedantic)]
// 使来自 clippy的 `filter_map` lint 的警告静音
#![allow(clippy::filter_map)]

fn main() {
    // ...
}

// 使 clippy的 `cmp_nan` lint 静音,仅用于此函数
#[allow(clippy::cmp_nan)]
fn foo() {
    // ...
}

注意:rustc 当前识别的工具类lint 有 "clippy" 和 "rustdoc"。

The deprecated attribute

deprecated属性

deprecated属性将程序项标记为已弃用。rustc 将对被 #[deprecated] 限定的程序项的行为发出警告。rustdoc 将特别展示已弃用的程序项,包括展示 since 版本和 note 提示(如果可用)。

deprecated属性有几种形式:

  • deprecated — 发布一个通用的弃用消息。
  • deprecated = "message" — 在弃用消息中包含给定的字符串。
  • MetaListNameValueStr元项属性句法形式里带有两个可选字段:
    • since — 指定程序项被弃用时的版本号。rustc 目前不解释此字符串,但是像 Clippy 这样的外部工具可以检查此值的有效性。
    • note — 指定一个应该包含在弃用消息中的字符串。这通常用于提供关于不推荐的解释和推荐首选替代。

deprecated属性可以应用于任何程序项trait项枚举变体结构体字段外部块项,或宏定义。它不能应用于 trait实现项。当应用于包含其他程序项的程序项时,如模块实现,所有子程序项都继承此 deprecation属性。

这有个例子:


#![allow(unused)]
fn main() {
#[deprecated(since = "5.2", note = "foo was rarely used. Users should instead use bar")]
pub fn foo() {}

pub fn bar() {}
}

RFC 内有动机说明和更多细节。

The must_use attribute

must_use属性

must_use属性 用于在值未被“使用”时发出诊断告警。它可以应用于用户定义的复合类型(结构体(struct)枚举(enum)联合体(union))、函数trait

must_use属性可以使用MetaNameValueStr元项属性句法添加一些附加消息,如 #[must_use = "example message"]。该字符串将出现在告警消息里。

当用户定义的复合类型上使用了此属性,如果有该类型的表达式正好是表达式语句的表达式,那就违反了 unused_must_use 这个 lint。


#![allow(unused)]
fn main() {
#[must_use]
struct MustUse {
    // 一些字段
}

impl MustUse {
  fn new() -> MustUse { MustUse {} }
}

// 违反 `unused_must_use` lint。
MustUse::new();
}

当函数上使用了此属性,如果此函数被当作表达式语句表达式调用表达式,那就违反了 unused_must_use lint。


#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }

// 违反 unused_must_use lint。
five();
}

当 [trait声明]中使用了此属性,如果表达式语句调用表达式返回了此 trait 的 trait实现(impl trait) ,则违反了 unused_must_use lint。


#![allow(unused)]
fn main() {
#[must_use]
trait Critical {}
impl Critical for i32 {}

fn get_critical() -> impl Critical {
    4i32
}

// 违反 `unused_must_use` lint。
get_critical();
}

当 trait声明中的一函数上使用了此属性时,如果调用表达式是此 trait 的某个实现中的该函数时,该行为同样违反 unused_must_use lint。


#![allow(unused)]
fn main() {
trait Trait {
    #[must_use]
    fn use_me(&self) -> i32;
}

impl Trait for i32 {
    fn use_me(&self) -> i32 { 0i32 }
}

// 违反 `unused_must_use` lint。
5i32.use_me();
}

当在 trait实现里的函数上使用 must_use属性时,此属性将被忽略。

注意:包含了此(属性应用的程序项产生的值)的普通空操作(no-op)表达式不会违反该 lint。例如,将此类值包装在没有实现 Drop 的类型中,然后不使用该类型,并成为未使用的块表达式的最终表达式(final expression)。


#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }

// 这些都不违反 unused_must_use lint。
(five(),);
Some(five());
{ five() };
if true { five() } else { 0i32 };
match true {
    _ => five()
};
}

注意:当一个必须使用的值被故意丢弃时,使用带有模式 _let语句是惯用的方法。


#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }

// 不违反 unused_must_use lint。
let _ = five();
}

代码生成属性

下述属性用于控制代码生成。

优化提示

cold属性inline属性给出了某种代码生成方式的提示建议,这种方式可能比没有此提示时更快。这些属性只是提示,可能会被忽略。

这两个属性都可以在函数上使用。当这类属性应用于 trait 中的函数上时,它们只在那些没有被 trait实现所覆盖的默认函数上生效,而不是所有 trait实现中用到的函数上都生效。这两属性对 trait 中那些没有函数体的函数没有影响。

内联(inline)属性

inline属性 的意义是暗示在调用者(caller)中放置此(属性限定的)函数的副本,而不是在定义此(属性限定的)函数的地方生此函数的代码,然后去让别处代码来调用此函数。

注意rustc 编译器会根据启发式算法(internal heuristics)1自动内联函数。不正确的内联函数会使程序变慢,所以应该小心使用此属性。

使用内联(inline)属性有三种方法:

  • #[inline] 暗示执行内联扩展。
  • #[inline(always)] 暗示应该一直执行内联扩展。
  • #[inline(never)] 暗示应该从不执行内联扩展。

注意: #[inline] 在每种形式中都是一个提示,不是必须要在调用者放置此属性限定的函数的副本。

cold属性

cold属性 暗示此(属性限定的)函数不太可能被调用。

no_builtins属性

no_builtins属性 可以应用在 crate 级别,用以禁用对假定存在的库函数调用的某些代码模式优化。2

target_feature属性

target_feature[属性] 可应用于非安全(unsafe)函数上,用来为特定的平台架构特性(platform architecture features)启用该函数的代码生成功能。它使用 MetaListNameValueStr元项属性句法来启用(该平台支持的)特性,但这次要求这个句法里只能有一个 enable键,其对应值是一个逗号分隔的由平台特性名字组成的符串。


#![allow(unused)]
fn main() {
#[cfg(target_feature = "avx2")]
#[target_feature(enable = "avx2")]
unsafe fn foo_avx2() {}
}

每个目标架构都有一组可以被启用的特性。为不是当前 crate 的编译目标下的CPU架构指定需启用的特性是错误的。

调用一个编译时启用了某特性的函数,但当前程序运行的平台并不支持该特性,那这将导致出现未定义行为

应用了 target_feature 的函数不会内联到不支持给定特性的上下文中。#[inline(always)] 属性不能与 target_feature属性一起使用。

可用特性

下面是可用特性列表。

x86 or x86_64

特性隐式启用描述中文描述
aessse2AES — Advanced Encryption Standard高级加密标准
avxsse4.2AVX — Advanced Vector Extensions高级矢量扩展指令集
avx2avxAVX2 — Advanced Vector Extensions 2高级矢量扩展指令集2
bmi1BMI1 — Bit Manipulation Instruction Sets位操作指令集
bmi2BMI2 — Bit Manipulation Instruction Sets 2位操作指令集2
fmaavxFMA3 — Three-operand fused multiply-add三操作乘加指令
fxsrfxsave and fxrstor — Save and restore x87 FPU, MMX Technology, and SSE State保存/恢复 x87 FPU、MMX技术,SSE状态
lzcntlzcnt — Leading zeros count前导零计数
pclmulqdqsse2pclmulqdq — Packed carry-less multiplication quadword压缩的四字(16字节)无进位乘法,主用于加解密处理
popcntpopcnt — Count of bits set to 1位1计数,即统计有多少个“为1的位”
rdrandrdrand — Read random number从芯片上的硬件随机数生成器中获取随机数
rdseedrdseed — Read random seed从芯片上的硬件随机数生成器中获取为伪随机数生成器设定的种子
shasse2SHA — Secure Hash Algorithm安全哈希算法
sseSSE — Streaming SIMD Extensions单指令多数据流扩展指令集
sse2sseSSE2 — Streaming SIMD Extensions 2单指令多数据流扩展指令集2
sse3sse2SSE3 — Streaming SIMD Extensions 3单指令多数据流扩展指令集3
sse4.1ssse3SSE4.1 — Streaming SIMD Extensions 4.1单指令多数据流扩展指令集4.1
sse4.2sse4.1SSE4.2 — Streaming SIMD Extensions 4.2单指令多数据流扩展指令集4.2
ssse3sse3SSSE3 — Supplemental Streaming SIMD Extensions 3增补单指令多数据流扩展指令集3
xsavexsave — Save processor extended states保存处理器扩展状态
xsavecxsavec — Save processor extended states with compaction压缩保存处理器扩展状态
xsaveoptxsaveopt — Save processor extended states optimizedxsave 指令集的优化版
xsavesxsaves — Save processor extended states supervisor保存处理器扩展状态监视程序

附加信息

请参阅 target_feature-条件编译选项,了解如何基于编译时的设置来有选择地启用或禁用对某些代码的编译。注意,条件编译选项不受 target_feature属性的影响,只是被整个 crate 启用的特性所驱动。

有关 x86 平台上的运行时特性检测,请参阅标准库中的 is_x86_feature_detected宏。

注意:rustc 为每个编译目标和 CPU 启用了一组默认特性。编译时,可以使用命令行参数 -C target-cpu 选择目标 CPU。可以通过命令行参数 -C target-feature 来为整个 crate 启用或禁用某些单独的特性。

track_caller属性

track_caller属性可以应用于除程序入口函数 fn main 之外的任何带有 "Rust" ABI 的函数。当此属性应用于 trait声明中的函数或方法时,该属性将应用在其所有的实现上。如果 trait 本身提供了带有该属性的默认函数实现,那么该属性也应用于其覆盖实现(override implementations)。

当应用于外部(extern)块中的函数上时,该属性也必须应用于此函数的任何链接实现(linked implementations)上,否则将导致未定义行为。当此属性应用在一个外部(extern)块内可用的函数上时,该外部(extern)块中的对该函数的声明也必须带上此属性,否则将导致未定义行为。

表现

将此属性应用到函数 f 上将允许 f 内的代码获得 f 被调用时建立的调用栈的“最顶层”的调用的位置(Location)信息的提示。从观察的角度来看,此属性的实现表现地就像从 f 所在的帧向上遍历调用栈,定位找到最近的有非此属性限定的调用函数 outer,并返回 outer 调用时的位置(Location)信息。


#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
}

注意:core 提供了 core::panic::Location::caller 来观察调用者的位置。它封装(wrap)了由 rustc 实现的内部函数(intrinsic) core::intrinsics::caller_location

注意:由于结果 Location 是一个提示,所以具体实现可能会提前终止对堆栈的遍历。请参阅限制以了解重要的注意事项。

示例

f 直接被 calls_f 调用时,f 中的代码观察其在 calls_f 内的调用位置:


#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
fn calls_f() {
    f(); // <-- f() 将打印此处的位置信息
}
}

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() 将两次打印此处的位置信息,一次是它自己,一次是此 f() 里来的
}
}

g 又被另一个有此属性限定的函数 h 调用,而g 又被 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(); // <-- 将三次打印此处的位置信息,一次是它自己,一次是此 g() 里来,一次是从 f() 里来的
}
}

以此类推。

限制

track_caller属性获取的信息是只是一个提示信息,实现不需要维护它。

特别是,将带有 #[track_caller] 的函数自动强转为函数指针会创建一个填充对象,该填充对象在观察者看来似乎是在此(属性限定的)函数的定义处调用的,从而在这层虚拟调用中丢失了实际的调用者信息。这种自动强转情况的一个常见示例是创建方法被此属性限定的 trait对象3

注意:前面提到的函数指针填充对象是必需的,因为 rustc 会通过向函数的 ABI 附加一个隐式参数来实现代码生成(codegen)上下文中的 track_caller,但这种添加是不健壮的(unsound),因为该隐式参数不是函数类型的一部分,那给定的函数指针类型可能引用也可能不引用具有此属性的函数。这里创建一个填充对象会对函数指针的调用方隐藏隐式参数,从而保持可靠性。

1

可字面理解为内部反复试探。

2

默认情况下,Rust 编译器会默认某些标准库函数在编译时可用,编译器也会把当前编译的代码往这些库函数可用的方向去优化。

3

因为 trait对象的值不能直接使用,只能自动强转为指针引用,那这里的调用就无法观察到真实的调用位置。

极值设置

以下属性影响部分编译期参数的极限值设置。

recursion_limit属性

recursion_limit属性可以应用于 crate 级别,为可能无限递归的编译期操作(如宏扩展或自动解引用)设置最大递归深度。它使用 MetaNameValueStr元项属性句法来指定递归深度。

注意:rustc 中这个参数的默认值是128。


#![allow(unused)]
#![recursion_limit = "4"]

fn main() {
macro_rules! a {
    () => { a!(1) };
    (1) => { a!(2) };
    (2) => { a!(3) };
    (3) => { a!(4) };
    (4) => { };
}

// 这无法扩展,因为它需要大于4的递归深度。
a!{}
}

#![allow(unused)]
#![recursion_limit = "1"]

fn main() {
// 这里的失败是因为需要两个递归步骤来自动解引用
(|_: &u8| {})(&&&1);
}

type_length_limit属性

type_length_limit属性限制在单态化过程中构造具体类型时所做的最大类型替换次数。它应用于 crate 级别,并使用 MetaNameValueStr元项属性句法来设置类型替换数量的上限。

注意:rustc 中这个参数的默认值是 1048576。

#![type_length_limit = "8"]

fn f<T>(x: T) {}

// 这里的编译失败是因为单态化 `f::<(i32, i32, i32, i32, i32, i32, i32, i32, i32)>>` 需要大于8个类型元素。
f((1, 2, 3, 4, 5, 6, 7, 8, 9));

类型系统属性

以下属性用于改变类型的使用方式。

non_exhaustive属性

non_exhaustive属性表示类型或变体将来可能会添加更多字段或变体。它可以应用在结构体(struct)上、枚举(enum)上 和 枚举变体上。

non_exhaustive属性使用 MetaWord元项属性句法,因此不接受任何输入。

在当前(non_exhaustive限制的类型的)定义所在的 crate 内,non_exhaustive 没有效果。


#![allow(unused)]
fn main() {
#[non_exhaustive]
pub struct Config {
    pub window_width: u16,
    pub window_height: u16,
}

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

// 非穷尽结构体可以在定义它的 crate 中正常构建。
let config = Config { window_width: 640, window_height: 480 };

// 非穷尽结构体可以在定义它的 crate 中进行详尽匹配
if let Config { window_width, window_height } = config {
    // ...
}

let error = Error::Other;
let message = Message::Reaction(3);

// 非穷尽枚举可以在定义它的 crate 中进行详尽匹配
match error {
    Error::Message(ref s) => { },
    Error::Other => { },
}

match message {
    // 非穷尽变体可以在定义它的 crate 中进行详尽匹配
    Message::Send { from, to, contents } => { },
    Message::Reaction(id) => { },
    Message::Quit => { },
}
}

在定义所在的 crate之外,标注为 non_exhaustive 的类型须在添加新字段或变体时保持向后兼容性。

非穷尽类型(non-exhaustive types)不能在定义它的 crate 之外构建:

示例:(译者注:本例把上例看成本例的 upstream

// `Config`、`Error` `Message`是在上游 crate 中定义的类型,这些类型已被标注为 `#[non_exhaustive]`。
use upstream::{Config, Error, Message};

// 不能构造 `Config` 的实例,如果在 `upstream` 的新版本中添加了新字段,则本地编译会失败,因此不允许这样做。
let config = Config { window_width: 640, window_height: 480 };

// 可以构造 `Error` 的实例,引入的新变体不会导致编译失败。
let error = Error::Message("foo".to_string());

// 无法构造 `Message::Send` 或 `Message::Reaction` 的实例,
// 如果在 `upstream` 的新版本中添加了新字段,则本地编译失败,因此不允许。
let message = Message::Send { from: 0, to: 1, contents: "foo".to_string(), };
let message = Message::Reaction(0);

// 无法构造 `Message::Quit` 的实例,
// 如果 `upstream` 内的 `Message::Quit` 的因为添加字段变成元组变体(tuple-variant/tuple variant)后,则本地编译失败。
let message = Message::Quit;

在定义所在的 crate 之外对非穷尽类型进行匹配,有如下限制:

  • 当模式匹配一个非穷尽变体(结构体(struct)枚举变体(enum variant))时,必须使用 StructPattern句法进行匹配,其匹配臂必须有一个为 ..。元组变体的构造函数的可见性降低为 min($vis, pub(crate))
  • 当模式匹配在一个非穷尽的枚举(enum)上时,增加对单个变体的匹配无助于匹配臂需满足枚举变体的穷尽性(exhaustiveness)的这一要求。

示例:(译者注:可以把上上例看成本例的 upstream

// `Config`、`Error` `Message` 是在上游 crate 中定义的类型,这些类型已被标注为 `#[non_exhaustive]`。
use upstream::{Config, Error, Message};

// 不包含通配符匹配臂,无法匹配非穷尽枚举。
match error {
  Error::Message(ref s) => { },
  Error::Other => { },
  // 加上 `_ => {},` 就能编译通过
}

// 不包含通配符匹配臂,无法匹配非穷尽结构体
if let Ok(Config { window_width, window_height }) = config {
    // 加上 `..` 就能编译通过
}

match message {
  // 没有通配符,无法匹配非穷尽(结构体/枚举内的)变体
  Message::Send { from, to, contents } => { },
  // 无法匹配非穷尽元组或单元枚举变体(unit enum variant)。
  Message::Reaction(type) => { },
  Message::Quit => { },
}

非穷尽类型最好放在下游 crate 里。

评论 (0)