文盤 Rust -- struct 中的生命週期

最近在用 rust 寫一個 redis 的數據校驗工具。redis-rs 中具備 redis::ConnectionLike trait,藉助它可以較好的來抽象校驗過程。在開發中,不免要定義 struct 中的某些元素爲 trait object,從而帶來一些 rust 語言中的生命週期問題。

首先來定義一個 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 問題。俗話講:"揹着抱着,一樣沉"。我本人還是傾向於把問題控制在上線之前,少折騰用戶。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/asyNd6JM6LFZQ2s-M5pWHw