< 返回我的博客

viruscamp 发表于 2022-10-28 00:06

NLL 导致的非直觉的作用域扩大

1. NLL 完全无视词法作用域

1.1. 是否可以认为 NLL 在fb.set(&b);处缩短了fb的起始生存期以配合b?
1.2. 应该认为 NLL 缩短了fb 还是延长了b 的生存期?

struct MyCell<T>(T);
impl<T> MyCell<T> {
    pub fn set(&mut self, t: T) {
        self.0 = t;
    }
}
let a = 3;
let mut fb = MyCell(&a); // 将 fb 绑定到 a 的生存期
let _ = {
    let b = 4;
    fb.set(&b);
    false
    // 直觉上 b 在此结束生存期
};
// 直觉上 fb 在此结束生存期
// NLL 智能的使 b 和 fb 的生存期同时结束, 使得 fb 可以使用 b

2. 强制延长外部变量生存期得到符合直觉的编译失败

struct MyCell<T>(T);
impl<T> MyCell<T> {
    pub fn set(&mut self, t: T) {
        self.0 = t;
    }
}
let a = 3;
let mut fb = MyCell(&a); // 将 fb 绑定到 a 的生存期
let _ = {
    let b = 4;
    fb.set(&b);
    false
    // b 在此结束生存期
};
fb; // 强制延长 fb 生存期

3. Drop导致隐式生存期延长而编译失败

主代码与 1 完全相同, 与 1 相比仅多一个空Drop, 而编译失败原因同 2

struct MyCell<T>(T);
impl<T> MyCell<T> {
    pub fn set(&mut self, t: T) {
        self.0 = t;
    }
}
impl<T> Drop for MyCell<T> {
    fn drop(&mut self) {}
}
let a = 3;
let mut fb = MyCell(&a); // 将 fb 绑定到 a 的生存期
let _ = {
    let b = 4;
    fb.set(&b);
    false
    // b 在此结束生存期
};
// drop 导致的隐式延长 fb 生存期

4. 编译失败 循环使得fb生存期不能缩短

有可能rust升级使其可通过编译

struct MyCell<T>(T);
impl<T> MyCell<T> {
    pub fn set(&mut self, t: T) {
        self.0 = t;
    }
}
let a = 3;
let mut fb = MyCell(&a); // 将 fb 绑定到 a 的生存期
let _ = loop {
    // loop 使得 fb 必须在此有效 而不能使 b 满足此要求
    let b = 4;
    fb.set(&b);
    if true { break false }
    // b 在此结束生存期
};
// fb 在此结束生存期

5. 编译成功 基本等同于 4

NLL 过于智能的消除了 loop

struct MyCell<T>(T);
impl<T> MyCell<T> {
    pub fn set(&mut self, t: T) {
        self.0 = t;
    }
}
let a = 3;
let mut fb = MyCell(&a); // 将 fb 绑定到 a 的生存期
let _ = loop {
    let b = 4;
    fb.set(&b);
    break false; // 过于智能的消除了 loop
};

评论区

写评论
作者 viruscamp 2022-10-28 21:58

我写的 4,5 两块代码都是循环, 都保证只运行一次, 却一个成功一个失败.
我认为要么就都能编译成功, 要么就都编译失败. 从语义和实现来看, 都让它失败更好.

--
👇
zylthinking: 循环中的 &b 未来也不可能通过编译; 基于以上规则, 只要 fb 有效, &b 就会有效, 哪怕是循环中通过 let 将 b 隐藏, 其结果不过是隐藏而已, 并不会 drop

这会导致每循环一次, 就会创建一个新的变量, 很快就会耗尽存储。

zylthinking 2022-10-28 18:31

fb.set(&b);处缩短了fb的起始生存期以配合b; 听起来像是运行时行为, 其实这是编译期分析。

也并不是缩短, 而是编译期分析代码, 确定 fb 生存期时选择了一个和 &b 相同的生命期, 具体来说, 此类选择总是选择所遇到的最短生命期

但是 drop 总是词法范围最后被调用, 不考虑 NLL, 因此只要有 drop 就会发生生存期冲突;

循环中的 &b 未来也不可能通过编译; 基于以上规则, 只要 fb 有效, &b 就会有效, 哪怕是循环中通过 let 将 b 隐藏, 其结果不过是隐藏而已, 并不会 drop

这会导致每循环一次, 就会创建一个新的变量, 很快就会耗尽存储。

1 共 2 条评论, 1 页