< 返回版块

TinusgragLin 发表于 2023-01-29 14:02

Tags:dropck,drop

我之前在看 nomicon 的 Drop Check 章节,但是马上就被第一个例子难住了,以下是一个简化的版本:

struct U8RefWithDrop<'a>(&'a u8);

impl<'a> Drop for U8RefWithDrop<'a> {
    fn drop(&mut self) {}
}

struct AStruct<'a> {
    u8ref: U8RefWithDrop<'a>,
    u8val: u8,
}

fn main() {
    let mut a = AStruct {
        u8val: 42,
        u8ref: U8RefWithDrop(&0),
    };
    a.u8ref = U8RefWithDrop(&a.u8val);
}

既然 RFC 1857 定义了 struct 中 fields 的 drop 顺序,例子里的 AStruct 也没有实现自定义的 Drop(如果有实现,Drop 函数中就可以按随机顺序 Drop 掉各 fields),那么为什么编译器仍要考虑 u8valu8refDrop 函数被调用之前就被 Drop 掉这种情况呢?

评论区

写评论
苦瓜小仔 2023-01-29 19:50

only generic types need to worry about this. If they aren't generic, then the only lifetimes they can harbor are 'static, which will truly live forever. This is why this problem is referred to as sound generic drop. Sound generic drop is enforced by the drop checker. As of this writing, some of the finer details of how the drop checker (also called dropck) validates types is totally up in the air. However The Big Rule is the subtlety that we have focused on this whole section:

For a generic type to soundly implement drop, its generics arguments must strictly outlive it.

对你的问题,从这段话找出几个重点(因为这段话是紧跟你的例子,你并没有理解):

  1. 只有泛型类型需要担心这个问题。(泛型类型是什么?答:带泛型参数的类型。泛型参数是什么?答:你写在 <> 尖括号里的东西,有生命周期参数、类型参数、常量参数。)

generic type: A generic type is a type with a generic parameter.

generic parameter: A generic parameter is a placeholder for a constant, a lifetime, or a type whose value is supplied statically by a generic argument.

  1. 如果泛型类型不是泛型,那么这个类型只有 'static 生命周期,它才真正活永远。(不是泛型,等价于不含泛型参数,当然无生命周期参数,从而不含借用;活永远,指想让它活多久就活多久,但需要考虑所有权)

  2. dropck 更深入的细节仍未确定。(Rust 采取保守的策略,只要在 safe Rust 中有可能出现不安全的情况,都不允许,除非使用 unsafe)

  3. 大原则是确定的:泛型类型安全地实现 drop,其泛型参数必须严格活得比这个泛型类型长!例子

#[test]
fn ok() {
    let v = 123;
    let mut a = AStruct {
        u8val: 42,
        u8ref: U8RefWithDrop(&0),
    }; // AStruct<'1>
    a.u8ref = U8RefWithDrop(&v); // '2 strictly outlives '1, &'2 v -> &'2 a.u8val -> &'1 a.u8val
}

fn main() {
    let mut a = AStruct {
        u8val: 42,
        u8ref: U8RefWithDrop(&0),
    };// AStruct<'1>
    a.u8ref = U8RefWithDrop(&a.u8val); // &'1 a -> &'1 a.u8val 不是严格活得久
}
//error[E0597]: `a.u8val` does not live long enough
//  --> src/lib.rs:30:29
//   |
//30 |     a.u8ref = U8RefWithDrop(&a.u8val); // &'1 a -> &'1 a.u8val
//   |                             ^^^^^^^^ borrowed value does not live long enough

怎么解决呢?文章后半部分就在说这个事。

比如,你可以使用 #[may_dangle],注意这是 unsafe 的。使用 #[may_dangle] 的前提:#[may_dangle] 用于保证 drop 函数不访问该借用 。如果你违反这个保证,代码是 UB。使用 run 和 miri 运行这个 例子,虽然表面上看代码正常运行,其实已经因为 unsafe 引入了 unsound。

一个常见的做法是手动 drop,使用 ManuallyDrop,miri 通过,注意 ManuallyDrop::drop 仍是 unsafe,你自己保证代码安全。

推荐我看到的一篇好的 dropck 文章(中文原创):https://ng6qpa7sht.feishu.cn/docx/LGnVdlqwGoIUpuxUMWRcptEbndd

ThalliMega 2023-01-29 14:52

Let's do this:

let tuple = (vec![], vec![]);

The left vector is dropped first. But does it mean the right one strictly outlives it in the eyes of the borrow checker? The answer to this question is no. The borrow checker could track fields of tuples separately, but it would still be unable to decide what outlives what in case of vector elements, which are dropped manually via pure-library code the borrow checker doesn't understand.

1 共 2 条评论, 1 页