公益广告 公司网站建设中...,科技与狠活是什么梗,石家庄最新消息,seo排名怎么做研读Rust圣经解析——Rust learn-16#xff08;高级trait#xff0c;宏#xff09; 高级trait关联类型Type为什么不用泛型而是Type 运算符重载#xff08;重要等级不高#xff09;重名方法消除歧义never typecontinue 的值是 ! 返回闭包 宏自定义宏#xff08;声明宏高级trait宏 高级trait关联类型Type为什么不用泛型而是Type 运算符重载重要等级不高重名方法消除歧义never typecontinue 的值是 ! 返回闭包 宏自定义宏声明宏宏的运作机制Rust编译过程新建空白宏宏选择器什么是词条树宏选择器设置各类入参 实现一个log宏运行重复模式匹配 自定义derive宏(过程宏)构建项目结构一定要照着做不然会错设置工作空间创建lib和main hello_macrolib.rs hello_macro_derive添加依赖和激活proc-macrolib.rs注意点请好好读官网上说的很清楚了这个地方一定要搞懂 pancakes添加依赖main.rs 错误cant find library marco_t, rename file to src/lib.rs or specify lib.path 为什么不能在单项目包里构建cant use a procedural macro from the same crate that defines it 自定义类属性宏个人认为最重要一个简单的例子项目包结构json_marco添加依赖编写lib json_test 一些例子baselibmain flaky_testlibmain json_parselib.rsmain.rs fn_timelibmain 高级trait
关联类型Type
我们使用type关键字即可声明一个关联类型关联类型的作用就是简化和隐藏显示类型个人认为
简化一个很长的类型总是被需要时需要开发者耗费精力的重复书写而且若有改动则需要改多个地方隐藏对外部调用者隐藏外部调用者无需知道它指的是什么只要可快速使用即可
trait test {type Res Resulti32, Boxstatic str;fn test_res() - Res{//...}
}为什么不用泛型而是Type
使用泛型我们就需要在每次使用实现时显示的标注类型但是当针对一个多处使用且无需修改类型的场景时无疑耗时耗力换而言之Type牺牲部分灵活度换取常用性
运算符重载重要等级不高
Rust 并不允许创建自定义运算符或重载任意运算符不过 std::ops 中所列出的运算符和相应的 trait 可以通过实现运算符相关 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 });
}
重名方法消除歧义
当我们实现多个trait的时候若遇到多个trait有同样的方法名那么就会产生重名歧义此时最晚实现的会覆盖前面的为了消除歧义我们可以采用trait::fn(type)来申明调用
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因为他表示函数从不返回的时候充当返回值
fn no_feedback()-!{//...
}continue 的值是 ! let guess: u32 match guess.trim().parse() {Ok(num) num,Err(_) continue,};返回闭包
一个函数的返回值是可以为一个闭包的这个没有限制具体来说我们简单了解写法即可
fn test()-Boxdyn Fn(){//...
}我们通过返回一个Box即将返回值写入堆中 例如
fn returns_closure() - Boxdyn Fn(i32) - i32 {Box::new(|x| x 1)
}
宏
从根本上来说宏是一种为写其他代码而写代码的方式即所谓的 元编程metaprogramming。在附录 C 中会探讨 derive 属性其生成各种 trait 的实现。我们也在本书中使用过 println! 宏和 vec! 宏。所有的这些宏以 展开 的方式来生成比你所手写出的更多的代码。
元编程对于减少大量编写和维护的代码是非常有用的它也扮演了函数扮演的角色。但宏有一些函数所没有的附加能力。
一个函数签名必须声明函数参数个数和类型。相比之下宏能够接收不同数量的参数用一个参数调用 println!(“hello”) 或用两个参数调用 println!(“hello {}”, name) 。而且宏可以在编译器翻译代码前展开例如宏可以在一个给定类型上实现 trait。而函数则不行因为函数是在运行时被调用同时 trait 需要在编译时实现。
实现宏不如实现函数的一面是宏定义要比函数定义更复杂因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性宏定义通常要比函数定义更难阅读、理解以及维护。
宏和函数的最后一个重要的区别是在一个文件里调用宏 之前 必须定义它或将其引入作用域而函数则可以在任何地方定义和调用。
—https://kaisery.github.io/trpl-zh-cn/ch19-06-macros.html
自定义宏声明宏
接下来我们就直接自定义宏少说废话直接开干为什么要学这个因为甚至可以使用这个自己写一门语言
宏的运作机制 Rust编译过程 新建空白宏
创建空白宏的方式很简单直接使用macro_rules!进行声明内部形似模式匹配推断其实根本就是
macro_rules! test {() {};
}宏选择器
item条目例如函数、结构、模块等block代码块stmt语句pat模式expr表达式ty类型ident标识符path路径例如 foo、 ::std::mem::replace, transmute::_, int, …meta元信息条目例如 #[…]和 #![rust macro…] 属性tt词条树
什么是词条树
tt词条树是指Rust编译器使用的一种数据结构通常用于处理宏(Macro)和代码生成(Code Generation)。
tt指的是Token Tree它是由一系列Token构成的树形结构。Token是编程语言中最基础的语法单元例如关键字、标识符、运算符、括号等等。而Token Tree则是这些Token按一定的层次结构排列而成的树。
在Rust语言中宏通常是使用tt词条树作为输入它可以让宏定义更加灵活和强大。通过对tt词条树进行递归、遍历和变换宏可以生成代码实现元编程(Metaprogramming)的效果。
除了宏之外Rust编译器还会使用tt词条树来处理一些代码生成工作例如构建抽象语法树(AST)或者生成代码的中间表示(IR)等等。
宏选择器设置各类入参
我们通过挑选适合的宏选择器才能对应我们宏接受的参数
实现一个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);
}运行重复模式匹配
当我们有多个入参的时候就需要用到这个了比如println这个宏我们可能会传入多个需要打印的内容如果各个要取个名字那么这样为什么还要去编写一个统一的简化的宏呢 重复模式匹配语法
($($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!(hellohi,nono);
}自定义derive宏(过程宏)
与上面的不一样的是这个derive宏标注的位置在一般在结构体、enum上 比如:
#[derive(Debug)]
struct a{}构建项目结构一定要照着做不然会错
以下是官方案例我做了一遍之后重写顺序并强调犯错点请大家一定要按照顺序做遇到错误查看我这里写的错误
设置工作空间
首先随便创建一个项目然后修改toml文件
hello_macro声明需要实现的traithello_macro_derive具体的解析转化处理逻辑pancakes主执行包
[workspace]
members[hello_macro,hello_macro_derive,pancakes
]创建lib和main
cargo new hello_macro --lib
cargo new hello_macro_derive --lib
cargo new pancakes结构如下图
hello_macro
lib.rs
书写需要实现的trait并使用pub暴露
pub trait HelloMacro {fn hello_macro();
}hello_macro_derive
添加依赖和激活proc-macro
syn crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构。quote 则将 syn 解析的数据结构转换回 Rust 代码。这些 crate 让解析任何我们所要处理的 Rust 代码变得更简单为 Rust 编写整个的解析器并不是一件简单的工作。 proc-macro表示这个cratq是一个proc-macro增加这个配置以后这个crate的特性就会发生一些变化例如这个crate将只能对外导出内部定义的过程宏而不能导出内部定义的其他内容。
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-macrotrue[dependencies]
quote 1.0.26
syn 2.0.15lib.rs
#[proc_macro_derive(HelloMacro)]标识只要是结构体、enum上标注#[derive(HelloMacro)]后就会自动实现HelloMacro这个trait具体的实现逻辑实际上在impl_hello_macro函数中
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 manipulatelet ast syn::parse(input).unwrap();// Build the trait implementationimpl_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()
}
注意点请好好读官网上说的很清楚了这个地方一定要搞懂
当用户在一个类型上指定 #[derive(HelloMacro)]时hello_macro_derive 函数将会被调用。因为我们已经使用 proc_macro_derive 及其指定名称HelloMacro对 hello_macro_derive 函数进行了注解指定名称HelloMacro就是 trait 名这是大多数过程宏遵循的习惯。
该函数首先将来自 TokenStream 的 input 转换为一个我们可以解释和操作的数据结构。这正是 syn 派上用场的地方。syn 中的 parse 函数获取一个 TokenStream 并返回一个表示解析出 Rust 代码的 DeriveInput 结构体。以下展示了从字符串 struct Pancakes; 中解析出来的 DeriveInput 结构体的相关部分
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 函数其用于构建所要包含在内的 Rust 新代码。但在此之前注意其输出也是 TokenStream。所返回的 TokenStream 会被加到我们的 crate 用户所写的代码中因此当用户编译他们的 crate 时他们会通过修改后的 TokenStream 获取到我们所提供的额外功能。
当调用 syn::parse 函数失败时我们用 unwrap 来使 hello_macro_derive 函数 panic。在错误时 panic 对过程宏来说是必须的因为 proc_macro_derive 函数必须返回 TokenStream 而不是 Result以此来符合过程宏的 API。这里选择用 unwrap 来简化了这个例子在生产代码中则应该通过 panic! 或 expect 来提供关于发生何种错误的更加明确的错误信息
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 为什么不能在单项目包里构建
若你仅仅在一个包中构建当你添加[lib] proc-macro true你会出现以下错误:
Caused by:cant find library marco_t, rename file to src/lib.rs or specify lib.path这说明我们不能把当前的包作为lib因为是主执行包 原理︰考虑过程宏是在编译一个crate之前对crate的代码进行加工的一段程序这段程序也是需要编译后执行的。如果定义过程宏和使用过程宏的代码写在一个crate中那就陷入了死锁: 要编译的代码首先需要运行过程宏来展开否则代码是不完整的没法编译crate. 不能编译cratecrate中的过程宏代码就没法执行就不能展开被过程宏装饰的代码
can’t use a procedural macro from the same crate that defines it
那假如直接去掉不管这个你会看到这个错误意味着你必须将过程宏构建在lib中
自定义类属性宏个人认为最重要
类属性宏与自定义派生宏相似不同的是 derive 属性生成代码它们类属性宏能让你创建新的属性。它们也更为灵活derive 只能用于结构体和枚举属性还可以用于其它的项比如函数 常见于各类框架中 一个简单的例子
项目包结构
同自定义过程宏 我们需要把正在的解析处理逻辑放在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映引入
[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
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;// 构建实现代码let expanded quote! {impl #name {fn my_function(self) {println!(This is my custom function!);}}};// 将生成的代码转换回 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_fnfor 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 结构体和枚举的通用 ASTlet input parse_macro_input!(input as DeriveInput);// 检查这是否是一个结构体并拿到它的名称、字段列表等信息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!),};// 生成代码将结构体转换为 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 {// 将输入解析为函数节点let input parse_macro_input!(input as ItemFn);// 获取函数名称、参数列表等信息let func_name input.ident;let func_args input.decl.inputs;// 生成代码在函数开始和结束时分别打印时间戳let output quote! {#inputfn #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);
}