<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國際加速解決方案。 廣告
                到目前為止你已經學到了不少Rust提供的抽象和重用代碼的工具了。這些代碼重用單元有豐富的語義結構。例如,函數有類型標記,類型參數有特性限制并且能重載的函數必須屬于一個特定的特性。 這些結構意味著Rust核心抽象擁有強大的編譯時正確性檢查。不過作為代價的是靈活性的減少。如果你識別出一個重復代碼的模式,你會發現把它們解釋為泛型函數,特性或者任何Rust語義中的其它結構很難或者很麻煩。 宏允許我們在_句法_水平上進行抽象。宏是一個“可擴展”句法形式的速記。這個擴展發生在編譯的早期,在任何靜態檢查之前。因此,宏可以實現很多Rust核心抽象不能做到的代碼重用模式。 缺點是基于宏的代碼更難懂,因為它很少利用Rust的內建規則。就像一個常規函數,一個通用的宏可以在不知道其實現的情況下使用。然而,設計一個通用的宏困難的!另外,在宏中的編譯錯誤更難解釋,因為它在擴展代碼上描述問題,惡如不是在開發者使用的代碼級別。 這些缺點讓宏成了所謂“最后求助于的功能”。這并不是說宏的壞話;只是因為它是Rust中需要真正簡明,良好抽象的代碼的部分。切記權衡取舍。 ## 定義一個宏 你可能見過`vec!`宏。用來初始化一個任意數量元素的[vector](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.17.Vectors.html)。 ~~~ let x: Vec<u32> = vec![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 }; ~~~ 我們可以使用宏來實現這么一個簡寫:[1](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.35.Macros%20%E5%AE%8F.html#1) ~~~ macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } ~~~ 哇哦,這里有好多新語法!讓我們分開來看。 ~~~ 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`都執行“一層”重復 2. 每個`$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宏則會有理想的表現: ~~~ 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簡寫所展示的那樣: ~~~ 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() { 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代碼中含有未擴展的宏,它也可以被解析為一個完整的[語法樹](http://kaisery.gitbooks.io/rust-book-chinese/content/content/7.Glossary%20%E8%AF%8D%E6%B1%87%E8%A1%A8.md#abstract-syntax-tree)。這個屬性對于編輯器或其它處理代碼的工具來說十分有用。這里也有一些關于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`;`&T` * `pat`:一個模式。例如:`Some(t)`;`(17, 'a')`;`_` * `stmt`:一個單獨語句。例如:`let x = 3` * `block`:一個大括號界定的語句序列。例如:`{ log(error, "hi"); return 12; }` * `item`:一個項。例如:`fn foo() { }`,`struct Bar` * `meta`:一個“元項”,可以在屬性中找到。例如:`cfg(target_os = "windows")` * `tt`:一個單獨的記號樹 對于一個元變量后面的一個記號有一些額外的規則: * `expr`變量必須后跟一個`=>`,`,`,`;` * `ty`和`path`變量必須后跟一個`=>`,`,`,`:`,`=`,`>`,`as` * `pat`變量必須后跟一個`=>`,`,`,`=` * 其它變量可以后跟任何記號 這些規則為Rust語法提供了一些靈活性以便將來的擴展不會破壞現有的宏。 宏系統完全不處理解析模糊。例如,`$($t:ty)* $e:expr`語法總是會解析失敗,因為解析器會被強制在解析`$t`和解析`$e`之間做出選擇。改變擴展在它們之前分別加上一個記號可以解決這個問題。在這個例子中,你可以寫成`$(T $t:ty)* E $e:exp`。 ## 范圍和宏導入/導出 宏在編譯的早期階段被展開,在命名解析之前。這有一個缺點是與語言中其它結構相比,范圍對宏的作用不一樣。 定義和擴展都發生在同一個深度優先,字典順序的包裝箱的代碼遍歷中。那么在模塊范圍內定義的宏對同模塊的接下來的代碼是可見的,這包括任何接下來的子`mod`項。 一個定義在`fn`函數體內的宏,或者任何其它不在模塊范圍內的地方,只在它的范圍內可見。 如果一個模塊有`subsequent`屬性,它的宏在子`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 ~~~ 當這個庫被用`#[macro_use] extern crate`裝載時,只有`m2`會被導入。 Rust參考中有一個[宏相關的屬性列表](http://doc.rust-lang.org/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) ) } ~~~ `inc_a`只能在`mylib`內工作,同時`inc_b`只能在庫外工作。進一步說,如果用戶有另一個名字導入`mylib`時`inc_b`將不能工作。 Rust(目前)還沒有針對包裝箱引用的衛生系統,不過它確實提供了一個解決這個問題的變通方法。當從一個叫`foo`的包裝箱總導入宏時,特殊宏變量`$crate`會展開為`::foo`。相反,當這個宏在同一包裝箱內定義和使用時,`$crate`將展開為空。這意味著我們可以寫 ~~~ #[macro_export] macro_rules! inc { ($x:expr) => ( $crate::increment($x) ) } ~~~ 來定義一個可以在庫內外都能用的宏。這個函數名字會展開為`::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`: ~~~ let v = vec![1, 2, 3, 4, 5]; ~~~ 它也讓你可以用重復值創建vector。例如,100個`0`: ~~~ let v = vec![0; 100]; ~~~ ### `assert!`和`assert_eq!` 這兩個宏用在測試中。`assert!`獲取一個布爾值,而`assert_eq!`獲取兩個值并比較它們。對了就通過,錯了就`panic!`(注:原書是Truth passes, success panic!s,個人認為不對)。像這樣: ~~~ // A-ok! assert!(true); assert_eq!(5, 3 + 2); // nope :( assert!(5 < 3); assert_eq!(5, 3); ~~~ ### `try!` `try!`用來進行錯誤處理。它獲取一些可以返回`Result`的數據,并返回`T`如果它是`Ok`,或`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宏系統不能做你想要的,你可能想要寫一個[編譯器插件](http://kaisery.gitbooks.io/rust-book-chinese/content/content/6.1.Compiler%20Plugins%20%E7%BC%96%E8%AF%91%E5%99%A8%E6%8F%92%E4%BB%B6.md)。與`macro_rules!`宏相比,它能做更多的事,接口也更不穩定,并且bug將更難以追蹤。相反你得到了可以在編譯器中運行任意Rust代碼的靈活性。為此語法擴展插件有時被稱為_宏程序_(_procedural macros_)。 * * * 1. 在libcollections中的`vec!`的實際定義與我們在這展示的有所不同,出于效率和可重用性的考慮。
                  <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>

                              哎呀哎呀视频在线观看