高級trait
關(guān)聯(lián)類型Type
我們使用type關(guān)鍵字即可聲明一個關(guān)聯(lián)類型,關(guān)聯(lián)類型的作用就是簡化和隱藏顯示類型(個人認(rèn)為)
- 簡化:一個很長的類型總是被需要時,需要開發(fā)者耗費精力的重復(fù)書寫,而且若有改動,則需要改多個地方
- 隱藏:對外部調(diào)用者隱藏,外部調(diào)用者無需知道它指的是什么,只要可快速使用即可
trait test {
type Res = Result<i32, Box<&'static str>>;
fn test_res() -> Res{
//...
}
}
為什么不用泛型而是Type
使用泛型,我們就需要在每次使用實現(xiàn)時顯示的標(biāo)注類型,但是當(dāng)針對一個多處使用且無需修改類型的場景時,無疑耗時耗力,換而言之,Type犧牲部分靈活度換取常用性
運(yùn)算符重載(重要等級不高)
Rust 并不允許創(chuàng)建自定義運(yùn)算符或重載任意運(yùn)算符,不過 std::ops 中所列出的運(yùn)算符和相應(yīng)的 trait 可以通過實現(xiàn)運(yùn)算符相關(guān) trait 來重載
因此我們就可以重載例如+,/,-,*
等,
以下是官方給出的例子:
use std::ops::Add;
#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}
重名方法消除歧義
當(dāng)我們實現(xiàn)多個trait的時候,若遇到多個trait有同樣的方法名,那么就會產(chǎn)生重名歧義,此時最晚實現(xiàn)的會覆蓋前面的,為了消除歧義,我們可以采用trait::fn(&type)
來申明調(diào)用
struct a {}
trait b {
fn get(&self) {}
}
trait c {
fn get(&self) {}
}
impl b for a {
fn get(&self) {
todo!()
}
}
impl c for a {
fn get(&self) {
todo!()
}
}
fn main() {
let a_struct = a {};
b::get(&a_struct);
c::get(&a_struct);
}
never type
Rust 有一個叫做 ! 的特殊類型,我們稱作never type因為他表示函數(shù)從不返回的時候充當(dāng)返回值
fn no_feedback()->!{
//...
}
continue 的值是 !
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
返回閉包
一個函數(shù)的返回值是可以為一個閉包的,這個沒有限制,具體來說我們簡單了解寫法即可
fn test()->Box<dyn Fn()>{
//...
}
我們通過返回一個Box即將返回值寫入堆中
例如:
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
宏
從根本上來說,宏是一種為寫其他代碼而寫代碼的方式,即所謂的 元編程(metaprogramming)。在附錄 C 中會探討 derive 屬性,其生成各種 trait 的實現(xiàn)。我們也在本書中使用過 println! 宏和 vec! 宏。所有的這些宏以 展開 的方式來生成比你所手寫出的更多的代碼。
元編程對于減少大量編寫和維護(hù)的代碼是非常有用的,它也扮演了函數(shù)扮演的角色。但宏有一些函數(shù)所沒有的附加能力。
一個函數(shù)簽名必須聲明函數(shù)參數(shù)個數(shù)和類型。相比之下,宏能夠接收不同數(shù)量的參數(shù):用一個參數(shù)調(diào)用 println!(“hello”) 或用兩個參數(shù)調(diào)用 println!(“hello {}”, name) 。而且,宏可以在編譯器翻譯代碼前展開,例如,宏可以在一個給定類型上實現(xiàn) trait。而函數(shù)則不行,因為函數(shù)是在運(yùn)行時被調(diào)用,同時 trait 需要在編譯時實現(xiàn)。
實現(xiàn)宏不如實現(xiàn)函數(shù)的一面是宏定義要比函數(shù)定義更復(fù)雜,因為你正在編寫生成 Rust 代碼的 Rust 代碼。由于這樣的間接性,宏定義通常要比函數(shù)定義更難閱讀、理解以及維護(hù)。
宏和函數(shù)的最后一個重要的區(qū)別是:在一個文件里調(diào)用宏 之前 必須定義它,或?qū)⑵湟胱饔糜?,而函?shù)則可以在任何地方定義和調(diào)用。
—https://kaisery.github.io/trpl-zh-cn/ch19-06-macros.html
自定義宏(聲明宏)
接下來我們就直接自定義宏,少說廢話,直接開干(為什么要學(xué)這個?因為甚至可以使用這個自己寫一門語言)
宏的運(yùn)作機(jī)制
Rust編譯過程
新建空白宏
創(chuàng)建空白宏的方式很簡單,直接使用macro_rules!
進(jìn)行聲明,內(nèi)部形似模式匹配推斷(其實根本就是)
macro_rules! test {
() => {};
}
宏選擇器
- item:條目,例如函數(shù)、結(jié)構(gòu)、模塊等
- block:代碼塊
- stmt:語句
- pat:模式
- expr:表達(dá)式
- ty:類型
- ident:標(biāo)識符
- path:路徑,例如 foo、 ::std::mem::replace, transmute::<_, int>, …
- meta:元信息條目,例如 #[…]和 #![rust macro…] 屬性
- tt:詞條樹
什么是詞條樹
tt詞條樹是指Rust編譯器使用的一種數(shù)據(jù)結(jié)構(gòu),通常用于處理宏(Macro)和代碼生成(Code Generation)。
tt指的是"Token Tree",它是由一系列"Token"構(gòu)成的樹形結(jié)構(gòu)。"Token"是編程語言中最基礎(chǔ)的語法單元,例如關(guān)鍵字、標(biāo)識符、運(yùn)算符、括號等等。而"Token Tree"則是這些"Token"按一定的層次結(jié)構(gòu)排列而成的樹。
在Rust語言中,宏通常是使用tt詞條樹作為輸入,它可以讓宏定義更加靈活和強(qiáng)大。通過對tt詞條樹進(jìn)行遞歸、遍歷和變換,宏可以生成代碼,實現(xiàn)元編程(Metaprogramming)的效果。
除了宏之外,Rust編譯器還會使用tt詞條樹來處理一些代碼生成工作,例如構(gòu)建抽象語法樹(AST)或者生成代碼的中間表示(IR)等等。
宏選擇器設(shè)置各類入?yún)?/h5>
我們通過挑選適合的宏選擇器,才能對應(yīng)我們宏接受的參數(shù)
實現(xiàn)一個log宏
use std::time::{Instant, SystemTime, UNIX_EPOCH};
macro_rules! log {
($log_name:tt)=>{
let now = SystemTime::now();
let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_secs();
println!("=======================start-{}=========================",$log_name);
println!("----------------createTime:{:?}",timestamp);
println!("----------------title:{}",$log_name);
println!("========================end-{}========================",$log_name);
};
}
fn main() {
log!("zhangsan");
}
運(yùn)行重復(fù)模式匹配
當(dāng)我們有多個入?yún)⒌臅r候就需要用到這個了,比如println!這個宏,我們可能會傳入多個需要打印的內(nèi)容,如果各個要取個名字,那么這樣為什么還要去編寫一個統(tǒng)一的,簡化的宏呢?
重復(fù)模式匹配語法:
($($x:expr),*)=>{}
use std::time::{Instant, SystemTime, UNIX_EPOCH};
macro_rules! eq_judge {
($($left:expr => $right:expr),*)=>{{
$(if $left == $right{
println!("true")
})*
}}
}
fn main() {
eq_judge!(
"hello"=>"hi",
"no"=>"no"
);
}
自定義derive宏(過程宏)
與上面的不一樣的是,這個derive宏標(biāo)注的位置在一般在結(jié)構(gòu)體、enum上
比如:
#[derive(Debug)]
struct a{}
構(gòu)建項目結(jié)構(gòu)(一定要照著做不然會錯)
以下是官方案例,我做了一遍之后重寫順序并強(qiáng)調(diào)犯錯點,請大家一定要按照順序做,遇到錯誤查看我這里寫的錯誤
設(shè)置工作空間
首先隨便創(chuàng)建一個項目,然后修改toml文件
- hello_macro:聲明需要實現(xiàn)的trait
- hello_macro_derive:具體的解析,轉(zhuǎn)化,處理邏輯
- pancakes:主執(zhí)行包
[workspace]
members=[
"hello_macro","hello_macro_derive","pancakes"
]
創(chuàng)建lib和main
cargo new hello_macro --lib
cargo new hello_macro_derive --lib
cargo new pancakes
結(jié)構(gòu)如下圖:
hello_macro
lib.rs
書寫需要實現(xiàn)的trait并使用pub暴露
pub trait HelloMacro {
fn hello_macro();
}
hello_macro_derive
添加依賴和激活proc-macro
syn crate 將字符串中的 Rust 代碼解析成為一個可以操作的數(shù)據(jù)結(jié)構(gòu)。quote 則將 syn 解析的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換回 Rust 代碼。這些 crate 讓解析任何我們所要處理的 Rust 代碼變得更簡單:為 Rust 編寫整個的解析器并不是一件簡單的工作。
proc-macro表示這個cratq是一個proc-macro,增加這個配置以后,這個crate的特性就會發(fā)生一些變化,例如,這個crate將只能對外導(dǎo)出內(nèi)部定義的過程宏,而不能導(dǎo)出內(nèi)部定義的其他內(nèi)容。
cargo add syn
cargo add quote
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro=true
[dependencies]
quote = "1.0.26"
syn = "2.0.15"
lib.rs
#[proc_macro_derive(HelloMacro)]
標(biāo)識只要是結(jié)構(gòu)體、enum上標(biāo)注#[derive(HelloMacro)]
后就會自動實現(xiàn)HelloMacro這個trait,具體的實現(xiàn)邏輯實際上在impl_hello_macro
函數(shù)中
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
注意點(請好好讀,官網(wǎng)上說的很清楚了,這個地方一定要搞懂)
當(dāng)用戶在一個類型上指定 #[derive(HelloMacro)]時,hello_macro_derive 函數(shù)將會被調(diào)用。因為我們已經(jīng)使用 proc_macro_derive 及其指定名稱HelloMacro對 hello_macro_derive 函數(shù)進(jìn)行了注解,指定名稱HelloMacro就是 trait 名,這是大多數(shù)過程宏遵循的習(xí)慣。
該函數(shù)首先將來自 TokenStream 的 input 轉(zhuǎn)換為一個我們可以解釋和操作的數(shù)據(jù)結(jié)構(gòu)。這正是 syn 派上用場的地方。syn 中的 parse 函數(shù)獲取一個 TokenStream 并返回一個表示解析出 Rust 代碼的 DeriveInput 結(jié)構(gòu)體。以下展示了從字符串 struct Pancakes; 中解析出來的 DeriveInput 結(jié)構(gòu)體的相關(guān)部分:
DeriveInput {
// --snip--
ident: Ident {
ident: "Pancakes",
span: #0 bytes(95..103)
},
data: Struct(
DataStruct {
struct_token: Struct,
fields: Unit,
semi_token: Some(
Semi
)
}
)
}
定義 impl_hello_macro 函數(shù),其用于構(gòu)建所要包含在內(nèi)的 Rust 新代碼。但在此之前,注意其輸出也是 TokenStream。所返回的 TokenStream 會被加到我們的 crate 用戶所寫的代碼中,因此,當(dāng)用戶編譯他們的 crate 時,他們會通過修改后的 TokenStream 獲取到我們所提供的額外功能。
當(dāng)調(diào)用 syn::parse 函數(shù)失敗時,我們用 unwrap 來使 hello_macro_derive 函數(shù) panic。在錯誤時 panic 對過程宏來說是必須的,因為 proc_macro_derive 函數(shù)必須返回 TokenStream 而不是 Result,以此來符合過程宏的 API。這里選擇用 unwrap 來簡化了這個例子;在生產(chǎn)代碼中,則應(yīng)該通過 panic! 或 expect 來提供關(guān)于發(fā)生何種錯誤的更加明確的錯誤信息
pancakes
添加依賴
這里我們需要依賴我們自己寫的lib所以需要用path指明
[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro_derive" }
main.rs
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
錯誤
can’t find library marco_t
, rename file to src/lib.rs
or specify lib.path (為什么不能在單項目包里構(gòu)建)
若你僅僅在一個包中構(gòu)建,當(dāng)你添加[lib] proc-macro = true
你會出現(xiàn)以下錯誤:
Caused by:
can't find library `marco_t`, rename file to `src/lib.rs` or specify lib.path
這說明我們不能把當(dāng)前的包作為lib,因為是主執(zhí)行包
原理︰考慮過程宏是在編譯一個crate之前,對crate的代碼進(jìn)行加工的一段程序,這段程序也是需要編譯后執(zhí)行的。如果定義過程宏和使用過程宏的代碼寫在一個crate中,那就陷入了死鎖:
要編譯的代碼首先需要運(yùn)行過程宏來展開,否則代碼是不完整的,沒法編譯crate.
不能編譯crate,crate中的過程宏代碼就沒法執(zhí)行,就不能展開被過程宏裝飾的代碼
can’t use a procedural macro from the same crate that defines it
那假如直接去掉不管這個,你會看到這個錯誤,意味著你必須將過程宏構(gòu)建在lib中
自定義類屬性宏(個人認(rèn)為最重要)
類屬性宏與自定義派生宏相似,不同的是 derive 屬性生成代碼,它們(類屬性宏)能讓你創(chuàng)建新的屬性。它們也更為靈活;derive 只能用于結(jié)構(gòu)體和枚舉;屬性還可以用于其它的項,比如函數(shù)
常見于各類框架中!
一個簡單的例子
項目包結(jié)構(gòu)
同自定義過程宏
我們需要把正在的解析處理邏輯放在lib下
[workspace]
members=[
"json_marco","json_test"
]
json_marco
添加依賴
[package]
name = "json_marco"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.56"
quote = "1.0.26"
syn = { version = "2.0.15", features = ["full"] }
編寫lib
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_attribute]
pub fn my_macro(attr:TokenStream,item:TokenStream)->TokenStream{
println!("test");
println!("{:#?}",attr);
println!("{:#?}",item);
item
}
這很簡單就是單純輸出一下
json_test
toml映引入文章來源:http://www.zghlxwxcb.cn/news/detail-432616.html
[package]
name = "json_test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
json_marco={path= "../json_marco"}
main.rs文章來源地址http://www.zghlxwxcb.cn/news/detail-432616.html
use json_marco::my_macro;
#[my_macro("test111")]
fn test(a: i32) {
println!("{}", a);
}
fn main() {
test(5);
}
一些例子
base
lib
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_attribute]
pub fn my_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
// 解析輸入的類型
let input = parse_macro_input!(item as DeriveInput);
// 獲取類型名
let name = input.ident;
// 構(gòu)建實現(xiàn)代碼
let expanded = quote! {
impl #name {
fn my_function(&self) {
println!("This is my custom function!");
}
}
};
// 將生成的代碼轉(zhuǎn)換回 TokenStream 以供返回
TokenStream::from(expanded)
}
main
#[my_macro]
struct MyStruct {
field1: u32,
field2: String,
}
fn main() {
let my_instance = MyStruct { field1: 42, field2: "hello".to_string() };
my_instance.my_function();
}
flaky_test
lib
extern crate proc_macro;
extern crate syn;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_attribute]
pub fn flaky_test(_attr: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = syn::parse_macro_input!(input as syn::ItemFn);
let name = input_fn.sig.ident.clone();
TokenStream::from(quote! {
#[test]
fn #name() {
#input_fn
for i in 0..3 {
println!("flaky_test retry {}", i);
let r = std::panic::catch_unwind(|| {
#name();
});
if r.is_ok() {
return;
}
if i == 2 {
std::panic::resume_unwind(r.unwrap_err());
}
}
}
})
}
main
#[flaky_test::flaky_test]
fn my_test() {
assert_eq!(1, 2);
}
json_parse
lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
#[proc_macro_attribute]
pub fn serde_json(_args: TokenStream, input: TokenStream) -> TokenStream {
// 將輸入解析為 DeriveInput 類型,這是所有 Rust 結(jié)構(gòu)體和枚舉的通用 AST
let input = parse_macro_input!(input as DeriveInput);
// 檢查這是否是一個結(jié)構(gòu)體,并拿到它的名稱、字段列表等信息
let struct_name = input.ident;
let fields = match input.data {
Data::Struct(data_struct) => data_struct.fields,
_ => panic!("'serde_json' can only be used with structs!"),
};
// 生成代碼,將結(jié)構(gòu)體轉(zhuǎn)換為 JSON 字符串
let output = match fields {
Fields::Named(fields_named) => {
let field_names = fields_named.named.iter().map(|f| &f.ident);
quote! {
impl #struct_name {
pub fn to_json(&self) -> String {
serde_json::to_string(&json!({
#(stringify!(#field_names): self.#field_names,)*
})).unwrap()
}
}
}
}
Fields::Unnamed(fields_unnamed) => {
let field_indices = 0..fields_unnamed.unnamed.len();
quote! {
impl #struct_name {
pub fn to_json(&self) -> String {
serde_json::to_string(&json!([
#(self.#field_indices,)*
])).unwrap()
}
}
}
}
Fields::Unit => {
quote! {
impl #struct_name {
pub fn to_json(&self) -> String {
serde_json::to_string(&json!({})).unwrap()
}
}
}
}
};
// 將生成的代碼作為 TokenStream 返回
output.into()
}
main.rs
#[serde_json]
struct MyStruct {
name: String,
age: u32,
}
fn main() {
let my_struct = MyStruct {
name: "Alice".to_string(),
age: 25,
};
let json_str = my_struct.to_json();
println!("JSON string: {}", json_str);
}
fn_time
lib
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn run(_args: TokenStream, input: TokenStream) -> TokenStream {
// 將輸入解析為函數(shù)節(jié)點
let input = parse_macro_input!(input as ItemFn);
// 獲取函數(shù)名稱、參數(shù)列表等信息
let func_name = &input.ident;
let func_args = &input.decl.inputs;
// 生成代碼,在函數(shù)開始和結(jié)束時分別打印時間戳
let output = quote! {
#input
fn #func_name(#func_args) -> () {
println!("{} started", stringify!(#func_name));
let start = std::time::Instant::now();
let result = #func_name(#func_args);
let end = start.elapsed();
println!("{} finished in {}ms", stringify!(#func_name), end.as_millis());
result
}
};
// 將生成的代碼作為 TokenStream 返回
output.into()
}
main
#[run]
fn my_function() -> i32 {
// 模擬一些處理時間
std::thread::sleep(std::time::Duration::from_secs(1));
42
}
fn main() {
let result = my_function();
println!("Result = {}", result);
}
到了這里,關(guān)于研讀Rust圣經(jīng)解析——Rust learn-16(高級trait,宏)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!