当前位置: 首页 > article >正文

rust宏系列教程-利用派生宏和属性宏增强struct功能

前面几篇文章已经详细介绍了Rust宏的概念、分类及如何自定义宏,本文介绍Struct及其他实现,然后通过两个示例展示如何在编译时修改struct代码,增强结构体功能。

Rust结构体及实现

Rust中的类属性和自定义派生宏用于获取Rust代码块,并在编译时以某种方式对其进行修改,通常是为了添加功能。为了理解Rust中的类属性宏和自定义派生宏,我们首先需要简单介绍一下Rust中的结构及其实现。下面的结构应该很容易理解。有趣的是,当我们创建操作特定结构体的函数时。我们的方法是使用impl:

struct Person {
    name: String,
    age: u8,
}

相关的函数和方法是在impl块内为结构体实现的。请看下面示例:

struct Person {
    age: u8,
    name: String,
}

// Implement a method `new()` for the `Person` struct, allowing initialization of a `Person` instance
impl Person {
    // Create a new `Person` with the provided `name` and `age`
    fn new(name: String, age: u8) -> Self {
        Person { name, age }
    }
    
    fn can_drink(&self) -> bool {
        if self.age >= 21 as u8 {
            return true;
        }
        return false;
    }

    fn age_in_one_year(&self) -> u8 {
        return &self.age + 1;
    }
} 

fn main() {
    // Usage: Create a new `Person` instance with a name and age
    let person = Person::new(String::from("Jesserc"), 19);

    // use some impl functions
    println!("{:?}", person.can_drink()); // false
    println!("{:?}", person.age_in_one_year()); // 20
    println!("{:?}", person.name);
}

使用:

// Usage: Create a new `Person` instance with a name and age
let person = Person::new(String::from("Jesserc"), 19);

// use some impl functions
person.can_drink(); // false
person.age_in_one_year(); // 20

Rust Traits

Rust特征是在不同的imps之间实现共享行为的一种方法。可以把它们看作是接口或抽象契约——任何使用接口或契约都必须实现某些功能。

例如,假设我们需要定义Car和Boat结构体的场景。我们想要附加一个方法,使我们能够以千米每小时为单位获取它们的速度。在Rust中,我们可以通过使用单个trait并在两个结构体之间共享方法来实现这一点。请看示例:

// Traits are defined with the `trait` keyword followed by their name
trait Speed {
    fn get_speed_kph(&self) -> f64;
}

// Car struct
struct Car {
    speed_mph: f64,
}

// Boat struct
struct Boat {
    speed_knots: f64,
}

// Traits are implemented for a type using the `impl` keyword as shown below
impl Speed for Car {
    fn get_speed_kph(&self) -> f64 {
        // Convert miles per hour to kilometers per hour
        self.speed_mph * 1.60934
    }
}

// We also implement the `Speed` trait for `Boat`
impl Speed for Boat {
    fn get_speed_kph(&self) -> f64 {
        // Convert knots to kilometers per hour
        self.speed_knots * 1.852
    }
}

fn main() {
    // Initialize a `Car` and `Boat` type
    let car = Car { speed_mph: 60.0 };
    let boat = Boat { speed_knots: 30.0 };

    // Get and print the speeds in kilometers per hour
    let car_speed_kph = car.get_speed_kph();
    let boat_speed_kph = boat.get_speed_kph();

    println!("Car Speed: {} km/h", car_speed_kph); // 96.5604 km/h
    println!("Boat Speed: {} km/h", boat_speed_kph); // 55.56 km/h
}

使用宏修改结构体

在我们的类函数宏教程中,我们看到了宏如何在大型Rust代码中扩展println!(…)和msg!(…)这样的代码。rust宏分为过程宏(有分为:函数式宏、派生宏以及属性宏),下面示例代码可以看到三者之间的调用差异:

在这里插入图片描述

为了直观地了解类属性宏的作用,我们将创建两个宏:自动记录函数调用日志,另一个用于删除字段。

自动记录函数调用日志

使用Syn,创建自动记录结构中的每个函数调用日志宏,这可以帮助调试代码。如果关系如何从头搭建工程,并创建过程宏,可以参考上篇博文:

#[proc_macro_attribute]
pub fn log_calls(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as syn::ItemFn);
    let func_name = &input.sig.ident;
    let func_block = &input.block;

    let expanded = quote! {
        fn #func_name() {
            println!("Calling function: {}", stringify!(#func_name));
            #func_block
        }
    };

    TokenStream::from(expanded)
}

对任何函数应用#[log_calls]都会自动插入一个println!语句,并在调用时记录函数的名称。log_calls宏将自动记录每个函数调用的名称。下面是一个实际应用的例子:

#[log_calls]
fn perform_action() {
    println!("Action performed.");
}

fn main() {
    perform_action();
}

运行上面程序输出结果:

Calling function: perform_action
Action performed.

读者可以修改逻辑,实现跟踪每个函数的调用时间进行业务优化非常有用。

派生宏自动增加Getter方法

有时,你希望为结构中的每个字段生成getter函数,而不需要手动编写它们。Syn允许读取结构体的字段并动态生成这些函数。

#[proc_macro_derive(Getters)]
pub fn getters(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    impl_getters(&input)
}

fn impl_getters(input: &DeriveInput) -> TokenStream {
    let name = &input.ident;
    
    let getters = if let syn::Data::Struct(data) = &input.data {
        data.fields.iter().map(|field| {
            let field_name = &field.ident;
            let field_type = &field.ty;
            
            quote! {
                pub fn #field_name(&self) -> &#field_type {
                    &self.#field_name
                }
            }
        })
    } else {
        return TokenStream::new();
    };

    let expanded = quote! {
        impl #name {
            #(#getters)*
        }
    };

    TokenStream::from(expanded)
}

将此宏应用于结构体会为结构体中的每个字段生成一个公共getter方法,从而实现对其字段的只读访问。

  • 调用派生宏生成getter方法

这个例子演示了getter宏如何为结构体中的每个字段生成getter方法。

#[derive(Getters)]
struct User {
    username: String,
    age: u8,
}

fn main() {
    let user = User { username: "Alice".to_string(), age: 30 };

    // Access fields using generated getter methods
    println!("Username: {}", user.username());
    println!("Age: {}", user.age());
}

Getters宏生成 username() 和age() 方法,使每个字段都可以访问,同时保持结构不可变。

总结

学以致用,在前面几篇博文详细介绍了如何自定义宏之后,本文结合实际应用场景给出有用示例。首先我们介绍了Rust结构体及过程宏,接着通过属性宏和派生宏示例展示如何动态生成代码,让你真正理解定义宏在编译时修改Rust结构体代码的价值和意义。希望对你有所启发,未来继续,一起rust。


http://www.kler.cn/a/410045.html

相关文章:

  • uniapp实现APP版本升级
  • 浅谈网络 | 传输层之TCP协议
  • cocos creator 3.8 打飞机Demo 9
  • Vscode 删除键删除失效
  • Pytest-Bdd-Playwright 系列教程(13):钩子(hooks)
  • ip代理池新玩法,收集全网可用代理01,初次验证存活ip
  • 如何判断注入点传参类型--理论
  • 分布式搜索引擎之elasticsearch单机部署与测试
  • 力扣第 63 题不同路径 II
  • Ollama使用感想
  • 4——单页面应用程序,vue-cli脚手架
  • Linux入门系列--查阅与统计
  • --- stream 数据流 java ---
  • 蓝网科技临床浏览系统存在SQL注入漏洞
  • HarmonyOS开发者社区有奖征文二期活动开启!
  • ‌Kotlin中的?.和!!主要区别
  • Spring Boot集成MyBatis-Plus:自定义拦截器实现动态表名切换
  • 【AI】基础原理
  • 第三百三十一节 Java网络教程 - Java网络UDP多播
  • PyTorch 分布式并行计算