为什么 Rust 要求 "一旦固定,一个值就必须永远保持固定"?

4

我对Pin<P>的要求感到困惑:

一旦定住,其值必须永远保持不变。

我在Reddit的讨论帖子中看到了这个问题,但是阅读完后我仍然感到困惑。

对我来说,如果我们要求指针在Pin<P>对象存在时被定住,但在Pin<P>对象被释放后可以重新获得移动它的能力,这似乎更自然。

以一个具体的例子为例:

use std::mem;
use std::pin::Pin;

fn move_pinned_ref<T>(mut a: T, mut b: T) {
    unsafe {
        let p: Pin<&mut T> = Pin::new_unchecked(&mut a);
        // *I prefer* it to mean that the pointee `a` cannot move when `p` is in the scope.
        // But *actually* it means the pointee `a` can never move again.
    }

    mem::swap(&mut a, &mut b);
    // *I prefer* it to be valid because `p` has been dropped.
    // But we *actually* have violated the pinning API contract that a value,
    // once pinned, must remain pinned forever.
}

能否有人指出,我喜欢的设计有什么问题吗?


2
如果有人能够评论一下为什么这个问题会收到负评,我将不胜感激。谢谢。 - Zhiyao
1个回答

3

这个规则的一个重要原因是它允许自引用结构体在Pin被丢弃后仍然保持有效。例如:

use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::NonNull;

struct Unmovable {
    data: String,
    slice: Option<NonNull<String>>,
    _pin: PhantomPinned,
}

impl Unmovable {
    fn update_slice(self: Pin<&mut Self>) {
        unsafe {
            let self_mut = Pin::get_unchecked_mut(self);
            self_mut.slice = Some(NonNull::from(&mut self_mut.data));
        }
    }
    
    fn print_slice(&self) {
        if let Some(s) = self.slice {
            unsafe {
                println!("{}", s.as_ref());
            }
        }
    }
}

fn main() {
    let mut a = Unmovable {
        data: "Hello, world!".into(),
        slice: None,
        _pin: PhantomPinned,
    };

    let p = unsafe { Pin::new_unchecked(&mut a) };
    p.update_slice(); // This causes `p` to be dropped.

    // If we move `a`, even after dropping the Pin:

    let x2 = Box::new(a);

    // Now x2 has a dangling pointer!
    x2.print_slice() // <-- Undefined behavior!
}

对我来说,这更像是一个指示,即 Pin p 应该与被固定的对象一样长寿;按照 OP 的语义,您必须确保在需要有效的自引用时不会丢弃 p,但是然后您可能会放弃该引用,"a" 就处于不安全的状态;但是然后您将移动、固定对象、调用 set_slice_range,它就可以再次工作了。是否可以扩展 Pin<P> 的 drop 行为,使其适用于特定类型的 P 并将 slice 重置为 None? - coredump
1
我更新了这个例子。问题在于,在调用 update_slice() 之前,您有一个悬空指针,如果您调用 print_slice() 将会导致 UB。您能否澄清您问题的其余部分? - Coder-256
我想说:当我删除“p”时,将“a.slice”设置为None,以便没有悬空指针,但我对Rust不够熟悉,不知道是否可以为类型Pin<P>定义drop()的清理操作,其中P是某种类型。 - coredump
1
不,您不能指定任何代码在“Pin”本身被删除时运行。 - Coder-256

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接