1. 程式人生 > >Mastering_Rust(譯):編譯器外掛(第九章)

Mastering_Rust(譯):編譯器外掛(第九章)

超程式設計的另一種形式,即編譯器外掛,可以在編譯時執行任意Rust程式碼。 這個特性是本書中唯一沒有達到Rust穩定版本的特性(也許永遠不會以這種形式出現),但它仍然被廣泛使用,並且應該涵蓋一個重要的差異化特徵。

本章預計會有一定程度的厚度和不確定性; 編譯器外掛是一個具有挑戰性的功能,它們的Rust實現仍然非常不穩定。 這些概念應該相當穩定,但即使在釋出日期後一年,實施細節也可能會有所不同。

在本章中,我們將介紹以下主題:

  • 最小的編譯器外掛
  • Cargo整合
  • 程式碼生成
  • Aster(用於建立抽象語法樹的庫)
  • Linter外掛
  • 巨集1.1
  • 巨集2.0

編譯器外掛的基礎知識

我們在上一章中看到的巨集在編譯時將語法轉換為另一種語法。 這是一個很好的工具,因為它允許多種形式的編譯時操作,同時提供乾淨和穩定的API。 但是,我們不能通過文字操作來完成大量令人滿意的編譯時間。 這裡有一些:

  • 額外的程式碼驗證檢查(lints)。
  • 編譯時驗證:資料庫架構檢查,主機名驗證。
  • 根據環境生成程式碼。 例如,從實時資料庫表建立資料模型,從環境填充資料結構,通過在編譯時計算昂貴的東西來優化執行時效能,等等。

雖然前一章中的巨集示例具有建立巨集的特殊語法,而模式匹配是中心結構,但編譯器外掛只是我們在編譯過程中包含的Rust函式。 使它們與眾不同的是它們採用預定義的形式將程式碼塊描述為AST並返回任意程式碼塊,再次編碼為AST。

以下是廣泛使用編譯器外掛的當前庫的列表:

diesel是一個安全,可擴充套件的資料庫物件關係對映器。 它使用編譯器外掛通過在編譯時連線到實際資料庫並讀取實時模式來生成和驗證資料庫介面模型程式碼。 在撰寫本書時,Diesel剛剛被移植到使用巨集1.1,所以當你讀到它時,它可能完全適用於穩定的Rust。

serde是一個通用的序列化/反序列化框架。 它使用編譯器外掛新增新的派生關鍵字Serialize和Deserialize,它可以從幾十種不同資料格式的結構中生成序列化和反序列化程式碼。 該框架可以將新格式流暢地新增為單獨的庫。 此外,Serde將針對巨集1.1,這意味著它應該已經完全可用於穩定的Rust。

rust-clippy通過數百個額外的檢查擴充套件了Rust編譯器。 這僅適用於夜間Rust,並且預計巨集1.1不足以滿足這些要求。

讓我們首先構建一個非常簡單的編譯器外掛

最小的編譯器外掛

通過Cargo構建編譯器外掛

程式碼生成作為變通方法

如前所述,編譯器外掛不是一個穩定的功能,但它們足夠有用,以便人們想要使用它們的功能。 有一種解決方法已經在穩定分支中執行:通過名為libsyntex的庫生成程式碼。 它的工作原理是將整個Rust編譯器捆綁在一個庫中並使用它來實現編譯器外掛。 這不是編譯器外掛的替代品,因為它們在夜間Rust中; 為了使程式碼生成方法起作用,需要對程式碼庫進行許多小的更改。

在實踐中,這通過在以.in結尾的模板檔案中移動具有編譯器外掛功能的任何程式碼模組來工作。 然後在編譯步驟之前使用單獨的構建指令碼(通常稱為build.rs)來生成擴充套件程式碼擴充套件的相同模組。 讓我們用你好的例子來試試吧。

首先,我們需要將程式碼樹重組為兩個 Cargo專案,一個持有外掛,另一個使用它。 這是樹的外觀:

在這裡插入圖片描述

需要稍微更改hello_plugin庫:

// compiler-plugin/stable/hello_plugin/src/lib.rs
extern crate syntex;
extern crate syntex_syntax;
use syntex_syntax::tokenstream::TokenTree;
use syntex_syntax::ext::base::{ExtCtxt, DummyResult, MacResult};
use syntex_syntax::ext::quote::rt::Span;
use syntex::Registry;
fn hello<'cx>(_: &'cx mut ExtCtxt, sp: Span, _: &[TokenTree])
-> Box<MacResult + 'cx> {
println!("Hello!");
DummyResult::any(sp)
} p
ub fn register(reg: &mut Registry) {
reg.add_macro("hello", hello);
}

主要是,crates命名不同(syntex_syntax而不僅僅是語法),巨集的生命週期現在是<'cx>而不是<'static>(因為使用syntex時靜態生命週期不可用)和Registry API call已從register_macro更改為add_macro。 API更改是編譯器外掛的一般不穩定性的一部分; 當你讀這篇文章時,它可能是其他的東西。 syntex有點像黑客,隨著巨集的穩定,它的使用應該會減少。 這是一個有用的中間步驟,有助於在今天的穩定編譯器上使用不穩定的巨集。