<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 宏 > [macros.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/macros.md) commit 7fd7b5b7fc628b3cd19c56daf84dbe9b2b9db1c0 到目前為止你已經學到了不少Rust提供的抽象和重用代碼的工具了。這些代碼重用單元有豐富的語義結構。例如,函數有類型簽名,類型參數有特性限制并且能重載的函數必須屬于一個特定的特性。 這些結構意味著Rust核心抽象擁有強大的編譯時正確性檢查。不過作為代價的是靈活性的減少。如果你識別出一個重復代碼的模式,你會發現把它們解釋為泛型函數,特性或者任何Rust語義中的其它結構很難或者很麻煩。 宏允許我們在*句法*水平上進行抽象。宏是一個“展開后的”句法形式的速記。這個展開發生在編譯的早期,在任何靜態檢查之前。因此,宏可以實現很多Rust核心抽象不能做到的代碼重用模式。 缺點是基于宏的代碼更難懂,因為它很少利用Rust的內建規則。就像常規函數,一個良好的宏可以在不知道其實現的情況下使用。然而,設計一個良好的宏困難的!另外,在宏中的編譯錯誤更難解釋,因為它在展開后的代碼上描述問題,不是在開發者使用的代碼級別。 這些缺點讓宏成了所謂“最后求助于的功能”。這并不是說宏的壞話;只是因為它是Rust中需要真正簡明,良好抽象的代碼的部分。切記權衡取舍。 ### 定義一個宏 你可能見過`vec!`宏。用來初始化一個任意數量元素的[vector](#)。 ~~~ let x: Vec<u32> = vec![1, 2, 3]; # assert_eq!(x, [1, 2, 3]); ~~~ 這不可能是一個常規函數,因為它可以接受任何數量的參數。不過我們可以想象的到它是這些代碼的句法簡寫: ~~~ let x: Vec<u32> = { let mut temp_vec = Vec::new(); temp_vec.push(1); temp_vec.push(2); temp_vec.push(3); temp_vec }; # assert_eq!(x, [1, 2, 3]); ~~~ 我們可以使用宏來實現這么一個簡寫:[實際上](#) ~~~ macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } # fn main() { # assert_eq!(vec![1,2,3], [1, 2, 3]); # } ~~~ 哇哦,這里有好多新語法!讓我們分開來看。 ~~~ macro_rules! vec { ... } ~~~ 這里我們定義了一個叫做`vec`的宏,跟用`fn vec`定義一個`vec`函數很相似。再羅嗦一句,我們通常寫宏的名字時帶上一個感嘆號,例如`vec!`。感嘆號是調用語法的一部分用來區別宏和常規函數。 ### 匹配 宏通過一系列*規則*定義,它們是模式匹配的分支。上面我們有: ~~~ ( $( $x:expr ),* ) => { ... }; ~~~ 這就像一個`match`表達式分支,不過匹配發生在編譯時Rust的語法樹中。最后一個分支(這里只有一個分支)的分號是可選的。`=>`左側的“模式”叫*匹配器*(*matcher*)。它有[自己的語法](http://doc.rust-lang.org/reference.html#macros)。 `$x:expr`匹配器將會匹配任何Rust表達式,把它的語法樹綁定到元變量`$x`上。`expr`標識符是一個*片段分類符*(*fragment specifier*)。在[宏進階章節](http://doc.rust-lang.org/book/advanced-macros.html)(已被本章合并,坐等官方文檔更新)中列舉了所有可能的分類符。匹配器寫在`$(...)`中,`*`會匹配0個或多個表達式,表達式之間用逗號分隔。 除了特殊的匹配器語法,任何出現在匹配器中的Rust標記必須完全相符。例如: ~~~ macro_rules! foo { (x => $e:expr) => (println!("mode X: {}", $e)); (y => $e:expr) => (println!("mode Y: {}", $e)); } fn main() { foo!(y => 3); } ~~~ 將會打印: ~~~ mode Y: 3 ~~~ 而這個: ~~~ foo!(z => 3); ~~~ 我們會得到編譯錯誤: ~~~ error: no rules expected the token `z` ~~~ ### 展開 宏規則的右邊是正常的Rust語法,大部分是。不過我們可以拼接一些匹配器中的語法。例如最開始的例子: ~~~ $( temp_vec.push($x); )* ~~~ 每個匹配的`$x`表達式都會在宏展開中產生一個單獨`push`語句。展開中的重復與匹配器中的重復“同步”進行(稍后介紹更多)。 因為`$x`已經在表達式匹配中聲明了,我們并不在右側重復`:expr`。另外,我們并不將用來分隔的逗號作為重復操作的一部分。相反,我們在重復塊中使用一個結束用的分號。 另一個細節:`vec!`宏的右側有*兩對*大括號。它們經常像這樣結合起來: ~~~ macro_rules! foo { () => {{ ... }} } ~~~ 外層的大括號是`macro_rules!`語法的一部分。事實上,你也可以`()`或者`[]`。它們只是用來界定整個右側結構的。 內層大括號是展開語法的一部分。記住,`vec!`在表達式上下文中使用。要寫一個包含多個語句,包括`let`綁定,的表達式,我們需要使用塊。如果你的宏只展開一個單獨的表達式,你不需要內層的大括號。 注意我們從未*聲明*宏產生一個表達式。事實上,直到宏被展開之前我們都無法知道。足夠小心的話,你可以編寫一個能在多個上下文中展開的宏。例如,一個數據類型的簡寫可以作為一個表達式或一個模式。 ### 重復(Repetition) 重復運算符遵循兩個原則: 1. `$(...)*`對它包含的所有`$name`都執行“一層”重復 1. 每個`$name`必須有至少這么多的`$(...)*`與其相對。如果多了,它將是多余的。 這個巴洛克宏展示了外層重復中多余的變量。 ~~~ macro_rules! o_O { ( $( $x:expr; [ $( $y:expr ),* ] );* ) => { &[ $($( $x + $y ),*),* ] } } fn main() { let a: &[i32] = o_O!(10; [1, 2, 3]; 20; [4, 5, 6]); assert_eq!(a, [11, 12, 13, 24, 25, 26]); } ~~~ 這就是匹配器的大部分語法。這些例子使用了`$(...)*`,它指“0次或多次”匹配。另外你可以用`$(...)+`代表“1次或多次”匹配。每種形式都可以包括一個分隔符,分隔符可以使用任何除了`+`和`*`的符號。 這個系統基于[Macro-by-Example](http://www.cs.indiana.edu/ftp/techreports/TR206.pdf)(PDF鏈接)。 ### 衛生(Hygiene) 一些語言使用簡單的文本替換來實現宏,它導致了很多問題。例如,這個C程序打印`13`而不是期望的`25`。 ~~~ #define FIVE_TIMES(x) 5 * x int main() { printf("%d\n", FIVE_TIMES(2 + 3)); return 0; } ~~~ 展開之后我們得到`5 * 2 + 3`,并且乘法比加法有更高的優先級。如果你經常使用C的宏,你可能知道標準的習慣來避免這個問題,或更多其它的問題。在Rust中,你不需要擔心這個問題。 ~~~ macro_rules! five_times { ($x:expr) => (5 * $x); } fn main() { assert_eq!(25, five_times!(2 + 3)); } ~~~ 元變量`$x`被解析成一個單獨的表達式節點,并且在替換后依舊在語法樹中保持原值。 宏系統中另一個常見的問題是*變量捕捉*(*variable capture*)。這里有一個C的宏,使用了[GNU C 擴展](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html)來模擬Rust表達式塊。 ~~~ #define LOG(msg) ({ \ int state = get_log_state(); \ if (state > 0) { \ printf("log(%d): %s\n", state, msg); \ } \ }) ~~~ 這是一個非常糟糕的用例: ~~~ const char *state = "reticulating splines"; LOG(state) ~~~ 它展開為: ~~~ const char *state = "reticulating splines"; { int state = get_log_state(); if (state > 0) { printf("log(%d): %s\n", state, state); } } ~~~ 第二個叫做`state`的參數參數被替換為了第一個。當打印語句需要用到這兩個參數時會出現問題。 等價的Rust宏則會有理想的表現: ~~~ # fn get_log_state() -> i32 { 3 } macro_rules! log { ($msg:expr) => {{ let state: i32 = get_log_state(); if state > 0 { println!("log({}): {}", state, $msg); } }}; } fn main() { let state: &str = "reticulating splines"; log!(state); } ~~~ 這之所以能工作時因為Rust有一個[衛生宏系統](http://en.wikipedia.org/wiki/Hygienic_macro)。每個宏展開都在一個不同的*語法上下文*(*syntax context*)中,并且每個變量在引入的時候都在語法上下文中打了標記。這就好像是`main`中的`state`和宏中的`state`被畫成了不同的“顏色”,所以它們不會沖突。 這也限制了宏在被執行時引入新綁定的能力。像這樣的代碼是不能工作的: ~~~ macro_rules! foo { () => (let x = 3); } fn main() { foo!(); println!("{}", x); } ~~~ 相反你需要在執行時傳遞變量的名字,這樣它會在語法上下文中被正確標記。 ~~~ macro_rules! foo { ($v:ident) => (let $v = 3); } fn main() { foo!(x); println!("{}", x); } ~~~ 這對`let`綁定和loop標記有效,對[items](http://doc.rust-lang.org/reference.html#items)無效。所以下面的代碼可以編譯: ~~~ macro_rules! foo { () => (fn x() { }); } fn main() { foo!(); x(); } ~~~ ### 遞歸宏 一個宏展開中可以包含更多的宏,包括被展開的宏自身。這種宏對處理樹形結構輸入時很有用的,正如這這個(簡化了的)HTML簡寫所展示的那樣: ~~~ # #![allow(unused_must_use)] macro_rules! write_html { ($w:expr, ) => (()); ($w:expr, $e:tt) => (write!($w, "{}", $e)); ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{ write!($w, "<{}>", stringify!($tag)); write_html!($w, $($inner)*); write!($w, "</{}>", stringify!($tag)); write_html!($w, $($rest)*); }}; } fn main() { # // FIXME(#21826) use std::fmt::Write; let mut out = String::new(); write_html!(&mut out, html[ head[title["Macros guide"]] body[h1["Macros are the best!"]] ]); assert_eq!(out, "<html><head><title>Macros guide</title></head>\ <body><h1>Macros are the best!</h1></body></html>"); } ~~~ ### 調試宏代碼 運行`rustc --pretty expanded`來查看宏展開后的結果。輸出表現為一個完整的包裝箱,所以你可以把它反饋給`rustc`,它會有時會比原版產生更好的錯誤信息。注意如果在同一作用域中有多個相同名字(不過在不同的語法上下文中)的變量的話`--pretty expanded`的輸出可能會有不同的意義。這種情況下`--pretty expanded,hygiene`將會告訴你有關語法上下文的信息。 `rustc`提供兩種語法擴展來幫助調試宏。目前為止,它們是不穩定的并且需要功能入口(feature gates)。 - `log_syntax!(...)`會打印它的參數到標準輸出,在編譯時,并且不“展開”任何東西。 - `trace_macros!(true)`每當一個宏被展開時會啟用一個編譯器信息。在展開后使用`trace_macros!(false)`來關閉它。 ### 句法要求 即使Rust代碼中含有未展開的宏,它也可以被解析為一個完整的[語法樹](#)。這個屬性對于編輯器或其它處理代碼的工具來說十分有用。這里也有一些關于Rust宏系統設計的推論。 一個推論是Rust必須確定,當它解析一個宏展開時,宏是否代替了 - 0個或多個項 - 0個或多個方法 - 一個表達式 - 一個語句 - 一個模式 一個塊中的宏展開代表一些項,或者一個表達式/語句。Rust使用一個簡單的規則來解決這些二義性。一個代表項的宏展開必須是 - 用大括號界定的,例如`foo! { ... }` - 分號結尾的,例如`foo!(...);` 另一個展開前解析的推論是宏展開必須包含有效的Rust記號。更進一步,括號,中括號,大括號在宏展開中必須是封閉的。例如,`foo!([)`是不允許的。這讓Rust知道宏何時結束。 更正式一點,宏展開體必須是一個*記號樹*(*token trees*)的序列。一個記號樹是一系列遞歸的 - 一個由`()`,`[]`或`{}`包圍的記號樹序列 - 任何其它單個記號 在一個匹配器中,每一個元變量都有一個*片段分類符*(*fragment specifier*),確定它匹配的哪種句法。 - `ident`:一個標識符。例如:`x`,`foo` - `path`:一個受限的名字。例如:`T::SpecialA` - `expr`:一個表達式。例如:`2 + 2`;`if true then { 1 } else { 2 }`;`f(42)` - `ty`:一個類型。例如:`i32`;`Vec<(char, String)>`;`&T` - `pat`:一個模式。例如:`Some(t)`;`(17, 'a')`;`_` - `stmt`:一個單獨語句。例如:`let x = 3` - `block`:一個大括號界定的語句序列。例如:`{ log(error, "hi"); return 12; }` - `item`:一個[項](http://doc.rust-lang.org/stable/reference.html#items)。例如:`fn foo() { }`,`struct Bar` - `meta`:一個“元數據項”,可以在屬性中找到。例如:`cfg(target_os = "windows")` - `tt`:一個單獨的記號樹 對于一個元變量(metavariable)后面的一個記號有一些額外的規則: - `expr`和`stmt`變量必須后跟任意一個:`=> , ;` - `ty`和`path`變量必須后跟任意一個:`=> , = | ; : > [ { as where` - `pat`變量必須后跟任意一個:`=> , = | if in` - 其它變量可以后跟任何記號 這些規則為 Rust 語法提供了一些靈活性以便將來的展開不會破壞現有的宏。 宏系統完全不處理解析模糊。例如,`$($t:ty)* $e:expr`語法總是會解析失敗,因為解析器會被強制在解析`$t`和解析`$e`之間做出選擇。改變展開在它們之前分別加上一個記號可以解決這個問題。在這個例子中,你可以寫成`$(T $t:ty)* E $e:exp`。 ### 范圍和宏導入/導出 宏在編譯的早期階段被展開,在命名解析之前。這有一個缺點是與語言中其它結構相比,范圍對宏的作用不一樣。 定義和展開都發生在同一個深度優先、字典順序的包裝箱的代碼遍歷中。那么在模塊范圍內定義的宏對同模塊的接下來的代碼是可見的,這包括任何接下來的子`mod`項。 一個定義在`fn`函數體內的宏,或者任何其它不在模塊范圍內的地方,只在它的范圍內可見。 如果一個模塊有`macro_use`屬性,它的宏在子`mod`項之后的父模塊也是可見的。如果它的父模塊也有`macro_use`屬性那么在父`mod`項之后的祖父模塊中也是可見的,以此類推。 `macro_use`屬性也可以出現在`extern crate`處。在這個上下文中它控制那些宏從外部包裝箱中裝載,例如 ~~~ #[macro_use(foo, bar)] extern crate baz; ~~~ 如果屬性只是簡單的寫成`#[macro_use]`,所有的宏都會被裝載。如果沒有`#[macro_use]`屬性那么沒有宏被裝載。只有被定義為`#[macro_export]`的宏可能被裝載。 裝載一個包裝箱的宏*而不*鏈接到輸出,使用`#[no_link]`。 一個例子: ~~~ macro_rules! m1 { () => (()) } // visible here: m1 mod foo { // visible here: m1 #[macro_export] macro_rules! m2 { () => (()) } // visible here: m1, m2 } // visible here: m1 macro_rules! m3 { () => (()) } // visible here: m1, m3 #[macro_use] mod bar { // visible here: m1, m3 macro_rules! m4 { () => (()) } // visible here: m1, m3, m4 } // visible here: m1, m3, m4 # fn main() { } ~~~ 當這個庫被用`#[macro_use] extern crate`裝載時,只有`m2`會被導入。 Rust參考中有一個[宏相關的屬性列表](http://doc.rust-lang.org/stable/reference.html#macro-related-attributes)。 ### `$crate`變量 當一個宏在多個包裝箱中使用時會產生另一個困難。來看`mylib`定義了 ~~~ pub fn increment(x: u32) -> u32 { x + 1 } #[macro_export] macro_rules! inc_a { ($x:expr) => ( ::increment($x) ) } #[macro_export] macro_rules! inc_b { ($x:expr) => ( ::mylib::increment($x) ) } # fn main() { } ~~~ `inc_a`只能在`mylib`內工作,同時`inc_b`只能在庫外工作。進一步說,如果用戶有另一個名字導入`mylib`時`inc_b`將不能工作。 Rust(目前)還沒有針對包裝箱引用的衛生系統,不過它確實提供了一個解決這個問題的變通方法。當從一個叫`foo`的包裝箱總導入宏時,特殊宏變量`$crate`會展開為`::foo`。相反,當這個宏在同一包裝箱內定義和使用時,`$crate`將展開為空。這意味著我們可以寫 ~~~ #[macro_export] macro_rules! inc { ($x:expr) => ( $crate::increment($x) ) } # fn main() { } ~~~ 來定義一個可以在庫內外都能用的宏。這個函數名字會展開為`::increment`或`::mylib::increment`。 為了保證這個系統簡單和正確,`#[macro_use] extern crate ...`應只出現在你包裝箱的根中,而不是在`mod`中。這保證了`$crate`展開為一個單獨的標識符。 ### 深入(The deep end) 之前的介紹章節提到了遞歸宏,但并沒有給出完整的介紹。還有一個原因令遞歸宏是有用的:每一次遞歸都給你匹配宏參數的機會。 作為一個極端的例子,可以,但極端不推薦,用Rust宏系統來實現一個[位循環標記](http://esolangs.org/wiki/Bitwise_Cyclic_Tag)自動機。 ~~~ macro_rules! bct { // cmd 0: d ... => ... (0, $($ps:tt),* ; $_d:tt) => (bct!($($ps),*, 0 ; )); (0, $($ps:tt),* ; $_d:tt, $($ds:tt),*) => (bct!($($ps),*, 0 ; $($ds),*)); // cmd 1p: 1 ... => 1 ... p (1, $p:tt, $($ps:tt),* ; 1) => (bct!($($ps),*, 1, $p ; 1, $p)); (1, $p:tt, $($ps:tt),* ; 1, $($ds:tt),*) => (bct!($($ps),*, 1, $p ; 1, $($ds),*, $p)); // cmd 1p: 0 ... => 0 ... (1, $p:tt, $($ps:tt),* ; $($ds:tt),*) => (bct!($($ps),*, 1, $p ; $($ds),*)); // halt on empty data string ( $($ps:tt),* ; ) => (()); } ~~~ 練習:使用宏來減少上面`bct!`宏定義中的重復。 ### 常用宏(Common macros) 這里有一些你會在Rust代碼中看到的常用宏。 ### `panic!` 這個宏導致當前線程恐慌。你可以傳給這個宏一個信息通過: ~~~ panic!("oh no!"); ~~~ ### `vec!` `vec!`的應用遍及本書,所以你可能已經見過它了。它方便創建`Vec<T>`: ~~~ let v = vec![1, 2, 3, 4, 5]; ~~~ 它也讓你可以用重復值創建vector。例如,100個`0`: ~~~ let v = vec![0; 100]; ~~~ ### `assert!`和`assert_eq!` 這兩個宏用在測試中。`assert!`獲取一個布爾值,而`assert_eq!`獲取兩個值并比較它們。`true` 就通過,`false`就`panic!`。像這樣: ~~~ // A-ok! assert!(true); assert_eq!(5, 3 + 2); // nope :( assert!(5 < 3); assert_eq!(5, 3); ~~~ ### `try!` `try!`用來進行錯誤處理。它獲取一些可以返回`Result<T, E>`的數據,并返回`T`如果它是`Ok<T>`,或`return`一個`Err(E)`如果出錯了。像這樣: ~~~ use std::fs::File; fn foo() -> std::io::Result<()> { let f = try!(File::create("foo.txt")); Ok(()) } ~~~ 它比這么寫要更簡明: ~~~ use std::fs::File; fn foo() -> std::io::Result<()> { let f = File::create("foo.txt"); let f = match f { Ok(t) => t, Err(e) => return Err(e), }; Ok(()) } ~~~ ### `unreachable!` 這個宏用于當你認為一些代碼不應該被執行的時候: ~~~ if false { unreachable!(); } ~~~ 有時,編譯器可能會讓你編寫一個你認為將永遠不會執行的不同分支。在這個例子中,用這個宏,這樣如果最終你錯了,你會為此得到一個`panic!`。 ~~~ let x: Option<i32> = None; match x { Some(_) => unreachable!(), None => println!("I know x is None!"), } ~~~ ### `unimplemented!` `unimplemented!`宏可以被用來當你嘗試去讓你的函數通過類型檢查,同時你又不想操心去寫函數體的時候。一個這種情況的例子是實現一個要求多個方法的特性,而你只想一次搞定一個。用`unimplemented!`定義其它的直到你準備好去寫它們了。 ### 宏程序(Procedural macros) 如果Rust宏系統不能做你想要的,你可能想要寫一個[編譯器插件](#)。與`macro_rules!`宏相比,它能做更多的事,接口也更不穩定,并且bug將更難以追蹤。相反你得到了可以在編譯器中運行任意Rust代碼的靈活性。為此語法擴展插件有時被稱為*宏程序*(*procedural macros*)。 實際上 > . `vec!` > 在 libcollections 中的實際定義跟這里的表現并不相同,出于效率和復用的考慮。 [ ?](# "Jump back to footnote [實際上] in the text.")
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看