[rust初见] 一个关于生命周期的问题

这个问题来自 practice.rs

一个问题

两行注释是编译器给出的报错

/* 使下面代码正常运行 */
struct Interface<'a> {
    manager: &'a mut Manager<'a>
}

impl<'a> Interface<'a> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

struct Manager<'a> {
    text: &'a str
}

struct List<'a> {
    manager: Manager<'a>,
}

impl<'a> List<'a> {
    pub fn get_interface(&'a mut self) -> Interface {
        Interface {
            manager: &mut self.manager
        }
    }
}

fn main() {
    let mut list = List {
        manager: Manager {
            text: "hello"
        }
    };

    list.get_interface().noop(); // mutable borrow occurs here

    println!("Interface should be dropped here and the borrow released");

    use_list(&list); // cannot borrow `list` as immutable because it is also borrowed as mutable
}

fn use_list(list: &List) {
    println!("{}", list.manager.text);
}

为什么可变引用的生命周期会持续到最后一行?

  1. 对于对象的引用 的生命周期是 小于等于 构成该对象的引用类型的字段 的生命周期的.
  2. self 本身的生命周期只需要大于等于 get_interface 内部 + Interface.manager 两者中更长的那一个生命周期.
  3. 但是 get_interface 函数中 self 的生命周期被标注为与 List::manager::text 相同的生命周期.
  4. 再加上后续 main 中还有对 list 的引用, 所以 self 的生命周期相当于被不合适地标注得更长了, 导致与后续的引用冲突.
    1. 这么说合适吗? 毕竟标注不会延长引用的生命周期
    2. 但是生命周期注明的 "规则" 会被编译器应用, 用于在无法推断生命周期之间的关系时去使用, 最终检查对象的生命周期是否与使用的实际情况符合.

如何改正?

  1. 首先需要收敛 self 的生命周期, 给它一个和底层 text &'a str 不同的生命周期.
  2. 又由于 List 中的 Manager<'a> 不是引用, 所以 List::manager 生命周期和 List 对象相同. 所以给 selftext 不同的生命周期会导致List::manager 的生命周期与 List::manager::text 的不同.
  3. 但是 Interface 的定义中, Interface::managerInterface::manager::text 被标注成相同的生命周期, 所以需要进一步改变这两者的生命周期关系, 将其分离.

最终的效果

struct Interface<'a, 'b> {
    manager: &'a mut Manager<'b>
}

impl Interface<'_, '_> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

struct Manager<'a> {
    text: &'a str
}

struct List<'a> {
    manager: Manager<'a>,
}

impl<'a> List<'a> {
    pub fn get_interface<'b>(&'b mut self) -> Interface<'b,'a> {
        Interface {
            manager: &mut self.manager
        }
    }
}

fn main() {
    let text = "hello";
    let mut list = List {
        manager: Manager {
            text
        }
    };
    list.get_interface().noop();
    println!("Interface should be dropped here and the borrow released");
    use_list(&list);
}

fn use_list(list: &List) {
    println!("{}", list.manager.text);
}

总结

  1. 对于结构体本身的引用 与 其底层引用类型的字段 两者的生命周期是不同的, 前者肯定小于等于后者, 而且一般使用的时候是小于的.
  2. 结构体中有引用字段的嵌套, 或者多个同级的引用字段, 需要考虑他们之间的生命周期关系.
    1. 对于上面的代码, Interface<'a, 'b>'a 'b 的关系没有注明, 但其实是有隐式的 'b : 'a 存在的.
    2. 如果颠倒过来, 就会导致没修改前相同的错误, 本质上还是让 self 的生命周期变大了.