属性

欢马劈雪     最近更新时间:2020-08-04 05:37:59

387

属性(Attribute)是一种通用的用于表达元数据的特性,借鉴ECMA-334(C#)的语法来实现ECMA-335中描述的Attributes。属性只能应用于Item(元素、项), 例如 use 声明、模块、函数等。

元素

在Rust中,Item是Crate(库)的一个组成部分。它包括

  • extern crate声明
  • use声明
  • 模块(模块是一个Item的容器)
  • 函数
  • type定义
  • 结构体定义
  • 枚举类型定义
  • 常量定义
  • 静态变量定义
  • Trait定义
  • 实现(Impl)

这些Item是可以互相嵌套的,比如在一个函数中定义一个静态变量、在一个模块中使用use声明或定义一个结构体。这些定义在某个作用域里面的Item跟你把 它写到最外层作用域所实现的功能是一样的,只不过你要访问这些嵌套的Item就必须使用路径(Path),如a::b::c。但一些外层的Item不允许你使用路径去 访问它的子Item,比如函数,在函数中定义的静态变量、结构体等,是不可以通过路径来访问的。

属性的语法

属性的语法借鉴于C#,看起来像是这样子的

#[name(arg1, arg2 = "param")]

它是由一个#开启,后面紧接着一个[],里面便是属性的具体内容,它可以有如下几种写法:

  • 单个标识符代表的属性名,如#[unix]
  • 单个标识符代表属性名,后面紧跟着一个=,然后再跟着一个字面量(Literal),组成一个键值对,如#[link(name = "openssl")]
  • 单个标识符代表属性名,后面跟着一个逗号隔开的子属性的列表,如#[cfg(and(unix, not(windows)))]

#后面还可以紧跟一个!,比如#![feature(box_syntax)],这表示这个属性是应用于它所在的这个Item。而如果没有!则表示这个属性仅应用于紧接着的那个Item。

例如:

// 为这个crate开启box_syntax这个新特性
#![feature(box_syntax)]

// 这是一个单元测试函数
#[test]
fn test_foo() {
    /* ... */
}

// 条件编译,只会在编译目标为Linux时才会生效
#[cfg(target_os="linux")]
mod bar {
    /* ... */
}

// 为以下的这个type定义关掉non_camel_case_types的编译警告
#[allow(non_camel_case_types)]
type int8_t = i8;

应用于Crate的属性

  • crate_name - 指定Crate的名字。如#[crate_name = "my_crate"]则可以让编译出的库名字为libmy_crate.rlib
  • crate_type - 指定Crate的类型,有以下几种选择

    • "bin" - 编译为可执行文件;
    • "lib" - 编译为库;
    • "dylib" - 编译为动态链接库;
    • "staticlib" - 编译为静态链接库;
    • "rlib" - 编译为Rust特有的库文件,它是一种特殊的静态链接库格式,它里面会含有一些元数据供编译器使用,最终会静态链接到目标文件之中。

    #![crate_type = "dylib"]

  • feature - 可以开启一些不稳定特性,只可在nightly版的编译器中使用。
  • no_builtins - 去掉内建函数。
  • no_main- 不生成main这个符号,当你需要链接的库中已经定义了main函数时会用到。
  • no_start - 不链接自带的native库。
  • no_std - 不链接自带的std库。
  • plugin - 加载编译器插件,一般用于加载自定义的编译器插件库。用法是

    // 加载foo, bar两个插件
    #![plugin(foo, bar)]
    // 或者给插件传入必要的初始化参数
    #![plugin(foo(arg1, arg2))]
  • recursive_limit - 设置在编译期最大的递归层级。比如自动解引用、递归定义的宏等。默认设置是#![recursive_limit = "64"]

应用于模块的属性

  • no_implicit_prelude - 取消自动插入use std::prelude::*
  • path - 设置此mod的文件路径。

    如声明mod a;,则寻找

    • 本文件夹下的a.rs文件
    • 本文件夹下的a/mod.rs文件
    #[cfg(unix)]
    #[path = "sys/unix.rs"]
    mod sys;
    
    #[cfg(windows)]
    #[path = "sys/windows.rs"]
    mod sys;

应用于函数的属性

  • main - 把这个函数作为入口函数,替代fn main,会被入口函数(Entry Point)调用。
  • plugin_registrar - 编写编译器插件时用,用于定义编译器插件的入口函数。
  • start - 把这个函数作为入口函数(Entry Point),改写 start language item。
  • test - 指明这个函数为单元测试函数,在非测试环境下不会被编译。
  • should_panic - 指明这个单元测试函数必然会panic。
  • cold - 指明这个函数很可能是不会被执行的,因此优化的时候特别对待它。
// 把`my_main`作为主函数
#[main]
fn my_main() {

}

// 把`plugin_registrar`作为此编译器插件的入口函数
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("rn", expand_rn);
}

// 把`entry_point`作为入口函数,不再执行标准库中的初始化流程
#[start]
fn entry_point(argc: isize, argv: *const *const u8) -> isize {

}

// 定义一个单元测试
// 这个单元测试一定会panic
#[test]
#[should_panic]
fn my_test() {
    panic!("I expected to be panicked");
}

// 这个函数很可能是不会执行的,
// 所以优化的时候就换种方式
#[cold]
fn unlikely_to_be_executed() {

}

应用于全局静态变量的属性

  • thread_local - 只可用于static mut,表示这个变量是thread local的。

应用于FFI的属性

extern块可以应用以下属性

  • link_args - 指定链接时给链接器的参数,平台和实现相关。
  • link - 说明这个块需要链接一个native库,它有以下参数:

    • name - 库的名字,比如libname.a的名字是name
    • kind - 库的类型,它包括
      • dylib - 动态链接库
      • static - 静态库
      • framework - OS X里的Framework
    #[link(name = "readline")]
    extern {
    
    }
    
    #[link(name = "CoreFoundation", kind = "framework")]
    extern {
    
    }

extern块里面,可以使用

  • link_name - 指定这个链接的外部函数的名字或全局变量的名字;
  • linkage - 对于全局变量,可以指定一些LLVM的链接类型( http://llvm.org/docs/LangRef.html#linkage-types )。

对于enum类型,可以使用

  • repr - 目前接受CC表示兼容C ABI。
#[repr(C)]
enum eType {
    Operator,
    Indicator,
}

对于struct类型,可以使用

  • repr - 目前只接受CpackedC表示结构体兼容C ABI,packed表示移除字段间的padding。

用于宏的属性

  • macro_use - 把模块或库中定义的宏导出来

    • 应用于mod上,则把此模块内定义的宏导出到它的父模块中
    • 应用于extern crate上,则可以接受一个列表,如

        #[macro_use(debug, trace)]
        extern crate log;

      则可以只导入列表中指定的宏,若不指定则导入所有的宏。

  • macro_reexport - 应用于extern crate上,可以再把这些导入的宏再输出出去给别的库使用。

  • macro_export - 应于在宏上,可以使这个宏可以被导出给别的库使用。

  • no_link - 应用于extern crate上,表示即使我们把它里面的库导入进来了,但是不要把这个库链接到目标文件中。

其它属性

  • export_function - 用于静态变量或函数,指定它们在目标文件中的符号名。

  • link_section - 用于静态变量或函数,表示应该把它们放到哪个段中去。

  • no_mangle - 可以应用于任意的Item,表示取消对它们进行命名混淆,直接把它们的名字作为符号写到目标文件中。

  • simd - 可以用于元组结构体上,并自动实现了数值运算符,这些操作会生成相应的SIMD指令。

  • doc - 为这个Item绑定文档,跟///的功能一样,用法是

    #[doc = "This is a doc"]
    struct Foo {}

条件编译

有时候,我们想针对不同的编译目标来生成不同的代码,比如在编写跨平台模块时,针对Linux和Windows分别使用不同的代码逻辑。

条件编译基本上就是使用cfg这个属性,直接看例子

#[cfg(target_os = "macos")]
fn cross_platform() {
    // Will only be compiled on Mac OS, including Mac OS X
}

#[cfg(target_os = "windows")]
fn cross_platform() {
    // Will only be compiled on Windows
}

// 若条件`foo`或`bar`任意一个成立,则编译以下的Item
#[cfg(any(foo, bar))]
fn need_foo_or_bar() {

}

// 针对32位的Unix系统
#[cfg(all(unix, target_pointer_width = "32"))]
fn on_32bit_unix() {

}

// 若`foo`不成立时编译
#[cfg(not(foo))]
fn needs_not_foo() {

}

其中,cfg可接受的条件有

  • debug_assertions - 若没有开启编译优化时就会成立。

  • target_arch = "..." - 目标平台的CPU架构,包括但不限于x86, x86_64, mips, powerpc, armaarch64

  • target_endian = "..." - 目标平台的大小端,包括biglittle

  • target_env = "..." - 表示使用的运行库,比如musl表示使用的是MUSL的libc实现, msvc表示使用微软的MSVC,gnu表示使用GNU的实现。 但在部分平台这个数据是空的。

  • target_family = "..." - 表示目标操作系统的类别,比如windowsunix。这个属性可以直接作为条件使用,如#[unix]#[cfg(unix)]

  • target_os = "..." - 目标操作系统,包括但不限于windows, macos, ios, linux, android, freebsd, dragonfly, bitrig, openbsd, netbsd

  • target_pointer_width = "..." - 目标平台的指针宽度,一般就是3264

  • target_vendor = "..." - 生产商,例如apple, pc或大多数Linux系统的unknown

  • test - 当启动了单元测试时(即编译时加了--test参数,或使用cargo test)。

还可以根据一个条件去设置另一个条件,使用cfg_attr,如

#[cfg_attr(a, b)]

这表示若a成立,则这个就相当于#[cfg(b)]

条件编译属性只可以应用于Item,如果想应用在非Item中怎么办呢?可以使用cfg!宏,如

if cfg!(target_arch = "x86") {

} else if cfg!(target_arch = "x86_64") {

} else if cfg!(target_arch = "mips") {

} else {

}

这种方式不会产生任何运行时开销,因为不成立的条件相当于里面的代码根本不可能被执行,编译时会直接被优化掉。

Linter参数

目前的Rust编译器已自带的Linter,它可以在编译时静态帮你检测不用的代码、死循环、编码风格等等。Rust提供了一系列的属性用于控制Linter的行为

展开阅读全文