我知道Pin
的原则是不公开其内部类型的可变引用。如果我想真正固定一个类型,我需要为它实现!Unpin
。
在做一些研究的时候,我知道我可以通过Pin::new_unchecked
创建一个!Unpin
对象。但是,我需要确保我们不能在P::Deref/DerefMut
中移动。如果我同时实现!Unpin
和一个实际移动内部字段的DerefMut
呢?会不会使Pin
不再起作用?
所以我写了下面的代码,它定义了一个EvilNUnpin
,它是一个指针,指向做坏事的字符串,它实际上改变了字符串b
,把它设置为3
.
我希望通过调用std::mem::swap
xp.deref()
和xp2.deref()
将等于3,因为我们已经调用了DerefMut
。
1.如果我们保存字符串x1.b
和x2.b
的指针,然后将其封装到Pin中。我认为我们最终应该观察到ptr1
和ptr2
的值最终为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
}
}
1条答案
按热度按时间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::Deref
和P::DerefMut
实现做出承诺(如果它们存在的话)。最重要的是,它们一定不能移出它们的self参数:Pin::as_mut
和Pin::as_ref
将在pinned指针上调用DerefMut::deref_mut
和Deref::deref
,并期望这些方法支持pinning不变量。您的实现清楚地移动了
DerefMut
内部的值--准确地说,它删除了这个值。因此,我们假设
EvilNUnpin
根本不可能被固定;换句话说,Pin::new_unchecked
是您看到的输出的罪魁祸首。