rust 如果我为一个也实现DerefMut的类型实现!Unpin,会怎么样?

piah890a  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(126)

我知道Pin的原则是不公开其内部类型的可变引用。如果我想真正固定一个类型,我需要为它实现!Unpin
在做一些研究的时候,我知道我可以通过Pin::new_unchecked创建一个!Unpin对象。但是,我需要确保我们不能在P::Deref/DerefMut中移动。如果我同时实现!Unpin和一个实际移动内部字段的DerefMut呢?会不会使Pin不再起作用?
所以我写了下面的代码,它定义了一个EvilNUnpin,它是一个指针,指向做坏事的字符串,它实际上改变了字符串b,把它设置为3 .
我希望通过调用std::mem::swap

  1. xp.deref()xp2.deref()将等于3,因为我们已经调用了DerefMut
    1.如果我们保存字符串x1.bx2.b的指针,然后将其封装到Pin中。我认为我们最终应该观察到ptr1ptr2的值最终为3。但是,它不是。我想知道为什么。

# [derive(Default, Debug)]

struct EvilNUnpin {
    b: String,
}
impl !Unpin for EvilNUnpin {}

impl Deref for EvilNUnpin {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.b
    }
}

impl DerefMut for EvilNUnpin {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.b = "3".to_owned();
        &mut self.b
    }
}

fn main() {
    let mut x1 = EvilNUnpin { b: "1".to_owned() };
    let mut x2 = EvilNUnpin { b: "2".to_owned() };
    let ptr1 = &x1.b as *const _ as isize;
    let ptr2 = &x2.b as *const _ as isize;
    // Note that we consider x1 and x2 as a pointer, so we don't use &x1
    let mut xp = unsafe { Pin::new_unchecked(x1) };
    let mut xp2 = unsafe { Pin::new_unchecked(x2) };
    std::mem::swap(&mut xp.as_mut(), &mut xp2.as_mut());
    assert_eq!(xp.deref(), "3");
    assert_eq!(xp2.deref(), "3");
    unsafe {
        let n1 = &*(ptr1 as *const String);
        let n2 = &*(ptr2 as *const String);
        assert_eq!(n1, "3"); // fire, actually 1
        assert_eq!(n2, "3"); // fire, actually 2
    }
}

也许是因为这里其实有一些复制品吧?

编辑

我已经更改了我的实现,并确保内部String没有被移动。我想检查的是,如果我打破了Pin::new_unchecked的约束,会发生什么。我认为!Unpin不会生效,所以我们将以交换两个值结束。但是,它仍然是不正确的。


# [derive(Debug)]

struct EvilNUnpin<'a> {
    b: &'a mut String,
}
impl !Unpin for EvilNUnpin<'_> {}

impl Deref for EvilNUnpin<'_> {
    type Target = String;
    fn deref(&self) -> &Self::Target {
        &self.b
    }
}

impl DerefMut for EvilNUnpin<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.b.push('a');
        &mut self.b
    }
}

fn main() {
    let mut s1 = "1".to_owned();
    let mut s2 = "2".to_owned();
    let ptr1 = &s1 as *const _ as isize;
    let ptr2 = &s2 as *const _ as isize;
    let mut x1 = EvilNUnpin { b: &mut s1 };
    let mut x2 = EvilNUnpin { b: &mut s2 };
    // Note that we don't use a reference.
    let mut xp = unsafe { Pin::new_unchecked(x1) };
    let mut xp2 = unsafe { Pin::new_unchecked(x2) };
    std::mem::swap(&mut xp.as_mut(), &mut xp2.as_mut());
    unsafe {
        let n1 = &*(ptr1 as *const String);
        let n2 = &*(ptr2 as *const String);
        assert_eq!(n1, "1a"); // why not 2a
        assert_eq!(n2, "2a"); // why not 1a
    }
}
q8l4jmvw

q8l4jmvw1#

Pin::as_mut上的文档(着重号为mine):
这是一个从&mut Pin<Pointer<T>>Pin<&mut T>的通用方法。它是安全的,因为作为Pin::new_unchecked契约的一部分,指针对象在Pin<Pointer<T>>创建后不能移动。Pointer::DerefMut的“恶意”实现同样被Pin::new_unchecked契约排除。
对于Pin::new_unchecked,我们可以看到:
这个建构函式是不安全的,因为我们无法保证指标所指向的数据是固定的,也就是说,除非卸除数据,否则不会移动数据或使其储存区无效。如果建构的Pin<P>无法保证P所指向的数据是固定的,则会违反API契约,并可能导致稍后(安全)作业中的未定义行为。
通过使用这个方法,您可以对P::DerefP::DerefMut实现做出承诺(如果它们存在的话)。最重要的是,它们一定不能移出它们的self参数Pin::as_mutPin::as_ref将在pinned指针上调用DerefMut::deref_mutDeref::deref,并期望这些方法支持pinning不变量。
您的实现清楚地移动了DerefMut内部的值--准确地说,它删除了这个值。
因此,我们假设EvilNUnpin根本不可能被固定;换句话说,Pin::new_unchecked是您看到的输出的罪魁祸首。

相关问题