屬性(Attribute)是一種通用的用于表達元數(shù)據(jù)的特性,借鑒ECMA-334(C#)的語法來實現(xiàn)ECMA-335中描述的Attributes。屬性只能應用于Item(元素、項),
例如 use
聲明、模塊、函數(shù)等。
在Rust中,Item是Crate(庫)的一個組成部分。它包括
extern crate
聲明use
聲明type
定義這些Item是可以互相嵌套的,比如在一個函數(shù)中定義一個靜態(tài)變量、在一個模塊中使用use
聲明或定義一個結構體。這些定義在某個作用域里面的Item跟你把
它寫到最外層作用域所實現(xiàn)的功能是一樣的,只不過你要訪問這些嵌套的Item就必須使用路徑(Path),如a::b::c
。但一些外層的Item不允許你使用路徑去
訪問它的子Item,比如函數(shù),在函數(shù)中定義的靜態(tài)變量、結構體等,是不可以通過路徑來訪問的。
屬性的語法借鑒于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)]
// 這是一個單元測試函數(shù)
#[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_name
- 指定Crate的名字。如#[crate_name = "my_crate"]
則可以讓編譯出的庫名字為libmy_crate.rlib
。crate_type
- 指定Crate的類型,有以下幾種選擇
"bin"
- 編譯為可執(zhí)行文件;"lib"
- 編譯為庫;"dylib"
- 編譯為動態(tài)鏈接庫;"staticlib"
- 編譯為靜態(tài)鏈接庫;"rlib"
- 編譯為Rust特有的庫文件,它是一種特殊的靜態(tài)鏈接庫格式,它里面會含有一些元數(shù)據(jù)供編譯器使用,最終會靜態(tài)鏈接到目標文件之中。例#![crate_type = "dylib"]
。
feature
- 可以開啟一些不穩(wěn)定特性,只可在nightly版的編譯器中使用。no_builtins
- 去掉內建函數(shù)。no_main
- 不生成main
這個符號,當你需要鏈接的庫中已經定義了main
函數(shù)時會用到。no_start
- 不鏈接自帶的native
庫。no_std
- 不鏈接自帶的std
庫。plugin
- 加載編譯器插件,一般用于加載自定義的編譯器插件庫。用法是
// 加載foo, bar兩個插件
#![plugin(foo, bar)]
// 或者給插件傳入必要的初始化參數(shù)
#![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
- 把這個函數(shù)作為入口函數(shù),替代fn main
,會被入口函數(shù)(Entry Point)調用。plugin_registrar
- 編寫編譯器插件時用,用于定義編譯器插件的入口函數(shù)。start
- 把這個函數(shù)作為入口函數(shù)(Entry Point),改寫 start
language item。test
- 指明這個函數(shù)為單元測試函數(shù),在非測試環(huán)境下不會被編譯。should_panic
- 指明這個單元測試函數(shù)必然會panic。cold
- 指明這個函數(shù)很可能是不會被執(zhí)行的,因此優(yōu)化的時候特別對待它。// 把`my_main`作為主函數(shù)
#[main]
fn my_main() {
}
// 把`plugin_registrar`作為此編譯器插件的入口函數(shù)
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_macro("rn", expand_rn);
}
// 把`entry_point`作為入口函數(shù),不再執(zhí)行標準庫中的初始化流程
#[start]
fn entry_point(argc: isize, argv: *const *const u8) -> isize {
}
// 定義一個單元測試
// 這個單元測試一定會panic
#[test]
#[should_panic]
fn my_test() {
panic!("I expected to be panicked");
}
// 這個函數(shù)很可能是不會執(zhí)行的,
// 所以優(yōu)化的時候就換種方式
#[cold]
fn unlikely_to_be_executed() {
}
thread_local
- 只可用于static mut
,表示這個變量是thread local的。extern
塊可以應用以下屬性
link_args
- 指定鏈接時給鏈接器的參數(shù),平臺和實現(xiàn)相關。link
- 說明這個塊需要鏈接一個native庫,它有以下參數(shù):
name
- 庫的名字,比如libname.a
的名字是name
;kind
- 庫的類型,它包括
dylib
- 動態(tài)鏈接庫static
- 靜態(tài)庫framework
- OS X里的Framework#[link(name = "readline")]
extern {
}
#[link(name = "CoreFoundation", kind = "framework")]
extern {
}
在extern
塊里面,可以使用
link_name
- 指定這個鏈接的外部函數(shù)的名字或全局變量的名字;linkage
- 對于全局變量,可以指定一些LLVM的鏈接類型( http://llvm.org/docs/LangRef.html#linkage-types )。對于enum
類型,可以使用
repr
- 目前接受C
,C
表示兼容C ABI。#[repr(C)]
enum eType {
Operator,
Indicator,
}
對于struct
類型,可以使用
repr
- 目前只接受C
和packed
,C
表示結構體兼容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
- 用于靜態(tài)變量或函數(shù),指定它們在目標文件中的符號名。
link_section
- 用于靜態(tài)變量或函數(shù),表示應該把它們放到哪個段中去。
no_mangle
- 可以應用于任意的Item,表示取消對它們進行命名混淆,直接把它們的名字作為符號寫到目標文件中。
simd
- 可以用于元組結構體上,并自動實現(xiàn)了數(shù)值運算符,這些操作會生成相應的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系統(tǒng)
#[cfg(all(unix, target_pointer_width = "32"))]
fn on_32bit_unix() {
}
// 若`foo`不成立時編譯
#[cfg(not(foo))]
fn needs_not_foo() {
}
其中,cfg
可接受的條件有
debug_assertions
- 若沒有開啟編譯優(yōu)化時就會成立。
target_arch = "..."
- 目標平臺的CPU架構,包括但不限于x86
, x86_64
, mips
, powerpc
, arm
或aarch64
。
target_endian = "..."
- 目標平臺的大小端,包括big
和little
。
target_env = "..."
- 表示使用的運行庫,比如musl
表示使用的是MUSL的libc實現(xiàn), msvc
表示使用微軟的MSVC,gnu
表示使用GNU的實現(xiàn)。
但在部分平臺這個數(shù)據(jù)是空的。
target_family = "..."
- 表示目標操作系統(tǒng)的類別,比如windows
和unix
。這個屬性可以直接作為條件使用,如#[unix]
,#[cfg(unix)]
。
target_os = "..."
- 目標操作系統(tǒng),包括但不限于windows
, macos
, ios
, linux
, android
, freebsd
, dragonfly
, bitrig
, openbsd
, netbsd
。
target_pointer_width = "..."
- 目標平臺的指針寬度,一般就是32
或64
。
target_vendor = "..."
- 生產商,例如apple
, pc
或大多數(shù)Linux系統(tǒng)的unknown
。
test
- 當啟動了單元測試時(即編譯時加了--test
參數(shù),或使用cargo test
)。還可以根據(jù)一個條件去設置另一個條件,使用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 {
}
這種方式不會產生任何運行時開銷,因為不成立的條件相當于里面的代碼根本不可能被執(zhí)行,編譯時會直接被優(yōu)化掉。
目前的Rust編譯器已自帶的Linter,它可以在編譯時靜態(tài)幫你檢測不用的代碼、死循環(huán)、編碼風格等等。Rust提供了一系列的屬性用于控制Linter的行為
allow(C)
- 編譯器將不會警告對于C
條件的檢查錯誤。deny(C)
- 編譯器遇到違反C
條件的錯誤將直接當作編譯錯誤。forbit(C)
- 行為與deny(C)
一樣,但這個將不允許別人使用allow(C)
去修改。warn(C)
- 編譯器將對于C
條件的檢查錯誤輸出警告。編譯器支持的Lint檢查可以通過執(zhí)行rustc -W help
來查看。
內聯(lián)函數(shù)即建議編譯器可以考慮把整個函數(shù)拷貝到調用者的函數(shù)體中,而不是生成一個call
指令調用過去。這種優(yōu)化對于短函數(shù)非常有用,有利于提高性能。
編譯器自己會根據(jù)一些默認的條件來判斷一個函數(shù)是不是應該內聯(lián),若一個不應該被內聯(lián)的函數(shù)被內聯(lián)了,實際上會導致整個程序更慢。
可選的屬性有:
#[inline]
- 建議編譯器內聯(lián)這個函數(shù)#[inline(always)]
- 要求編譯器必須內聯(lián)這個函數(shù)#[inline(never)]
- 要求編譯器不要內聯(lián)這個函數(shù)內聯(lián)會導致在一個庫里面的代碼被插入到另一個庫中去。
編譯器提供一個編譯器插件叫作derive
,它可以幫你去生成一些代碼去實現(xiàn)(impl)一些特定的Trait,如
#[derive(PartialEq, Clone)]
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
}
}
impl<T: Clone> Clone for Foo<T> {
fn clone(&self) -> Foo<T> {
Foo {
a: self.a.clone(),
b: self.b.clone(),
}
}
}
目前derive
僅支持標準庫中部分的Trait。
在非穩(wěn)定版的Rust編譯器中,可以使用一些不穩(wěn)定的功能,比如一些還在討論中的新功能、正在實現(xiàn)中的功能等。Rust編譯器提供一個應用于Crate的屬性feature
來啟用這些不穩(wěn)定的功能,如
#![feature(advanced_slice_patterns, box_syntax, asm)]
具體可使用的編譯器特性會因編譯器版本的發(fā)布而不同,具體請閱讀官方文檔。