• 文盘Rust -- struct 中的生命周期


    最近在用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 问题。俗话讲:"背着抱着,一样沉".我本人还是倾向于把问题控制在上线之前,少折腾用户。

    本期咱们先聊到这儿,下期见

  • 相关阅读:
    博途S7-1200PLC自由口通信(Send_P2P和Receive_P2P指令编程)
    【Spark | SparkStreaming】
    设计模式学习笔记 - 装饰者模式
    Pai到用时方恨少
    前端与后端传递数据 — — JSON
    Lit(五):事件
    C++基础知识要点--字符串、向量和数组
    Git Cherry Pick的使用
    Windows编译Chrome浏览器【多图警告^-^】
    转 股票触发指定价格发送到 企业微信
  • 原文地址:https://www.cnblogs.com/Jcloud/p/16768365.html