最近在用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 问题。俗话讲:"背着抱着,一样沉".我自己还是偏向于把问题管制在上线之前,少折腾用户。
本期咱们先聊到这儿,下期见