最近在用rust 写一个redis的数据校验工具 。redis-rs中具备 redis::ConnectionLike trait , 借助它可以较好的来抽象校验过程 。在开发中 , 不免要定义struct 中的某些元素为 trait object , 从而带来一些rust语言中的生命周期问题 。本文不具体讨论 redis的数据校验过程 , 通过一个简单的例子来聊聊 struct 中 trait object 元素的生命周期问题 。
首先来定义一个 base trait,该 trait 中只包含一个函数 , 返回String类型 。
pub trait Base {fn say(&self) -> String;}
接下来 , 定义两个实现了 Base trait 的 struct AFromBase 和 BFromBase
pub struct AFromBase {content: String,}impl Base for AFromBase {fn say(&self) -> String {self.content.clone()}}pub struct BFromBase {text: String,}impl Base for BFromBase {fn say(&self) -> String {self.text.clone()}}
接下来 , 定义一个struct 包含两个 Base trait 的 trait object , 然后实现一个函数是 say 函数输出的字符串的拼接结果.按照其他没有生命周期语言的编写习惯 , 直觉上这么写
pub struct AddTowBase {a: &mut dyn Base,b: &mut dyn Base,}impl AddTowBase {fn add(&self) -> String {let result = self.a.say() + &self.b.say();result}}
最后 , 搞个main函数验证一下 。完整代码如下
pub trait Base {fn say(&self) -> String;}pub struct AFromBase {content: String,}impl Base for AFromBase {fn say(&self) -> String {self.content.clone()}}pub struct BFromBase {text: String,}impl Base for BFromBase {fn say(&self) -> String {self.text.clone()}}pub struct AddTowBase {a: &mut dyn Base,b: &mut dyn Base,}impl<'a> AddTowBase<'a> {fn add(&self) -> String {let result = self.a.say() + &self.b.say();result}}fn main() {let mut a = AFromBase {content: "baseA".to_string(),};let mut b = BFromBase {text: "baseB".to_string(),};let addtow = AddTowBase {a: &mut a,b: &mut b,};let r = addtow.add();println!("{}", r);}
很遗憾 , 以上代码是不能编译通过的 , 编译时报如下错误
error[E0106]: missing lifetime specifier--> examples/lifetimeinstruct.rs:26:8|26 |a: &mut dyn Base,|^ expected named lifetime parameter|help: consider introducing a named lifetime parameter|25 ~ pub struct AddTowBase<'a> {26 ~a: &'a mut dyn Base,|error[E0106]: missing lifetime specifier--> examples/lifetimeinstruct.rs:27:8|27 |b: &mut dyn Base,|^ expected named lifetime parameter|help: consider introducing a named lifetime parameter|25 ~ pub struct AddTowBase<'a> {26 |a: &mut dyn Base,27 ~b: &'a mut dyn Base,|For more information about this error, try `rustc --explain E0106`.error: could not compile `wenpan-rust` due to 2 previous errors
编译器给出的提示很明确 , 要在 trait object 上添加生命周期参数 , 确保 struct 和他的 trait object 元素在同一生命周期 , 避免悬垂指针 。我们按照编译器的提示修改代码
pub struct AddTowBase<'a> {a: &'a mut dyn Base,b: &'a mut dyn Base,}impl<'a> AddTowBase<'a> {fn add(self) -> String {let result = self.a.say() + &self.b.say();result}}
代码顺利通过编译 。rust 的生命周期保证了内存的安全性 , 同时也增加了开发者的心智负担 。是在上线之前多费心思写代码 , 还是在上线以后忙忙活活查问题 , 这是个 trade off 问题 。俗话讲:"背着抱着 , 一样沉".我本人还是倾向于把问题控制在上线之前 , 少折腾用户 。
本期咱们先聊到这儿 , 下期见
【文盘Rust -- struct 中的生命周期】