将 Option<&mut T> 转换为 *mut T。

6
我正在编写 Rust 对 C 库的封装,期间试图利用《The Book》中提到的“可空指针优化”,但我找不到一个好的方法将 Option<&T> 转换为 *const TOption<&mut T> 转换为 *mut T,就像他们描述的那样。
我真正想要的是能够调用 Some(&foo) as *const _。不幸的是,这并不起作用,所以我能想到的下一个最好的方法是在 Option<T> 上定义一个 trait,使我能够调用 Some(&foo).as_ptr()。以下代码是该 trait 的工作定义和实现:
use std::ptr;

trait AsPtr<T> {
    fn as_ptr(&self) -> *const T;
}

impl<'a, T> AsPtr<T> for Option<&'a T> {
    fn as_ptr(&self) -> *const T {
        match *self {
            Some(val) => val as *const _,
            None => ptr::null(),
        }
    }
}

现在我可以调用Some(&foo).as_ptr()来获取一个*const _,我希望能够调用Some(&mut foo).as_ptr()来获取一个*mut _。以下是我创建的新特性:

trait AsMutPtr<T> {
    fn as_mut_ptr(&self) -> *mut T;
}

impl<'a, T> AsMutPtr<T> for Option<&'a mut T> {
    fn as_mut_ptr(&self) -> *mut T {
        match *self {
            Some(val) => val as *mut _,
            None => ptr::null_mut(),
        }
    }
}

问题在于,AsMutPtr特质无法编译。尝试时会得到以下错误:
error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:22:15
   |
22 |         match *self {
   |               ^^^^^
   |               |
   |               cannot move out of borrowed content
   |               help: consider removing the `*`: `self`
23 |             Some(val) => val as *mut _,
   |                  --- data moved here
   |
note: move occurs because `val` has type `&mut T`, which does not implement the `Copy` trait
  --> src/lib.rs:23:18
   |
23 |             Some(val) => val as *mut _,
   |                  ^^^

我不明白两个特征之间发生了什么变化导致它失败-我认为添加“mut”不会有太大的差别。我尝试添加一个“ref”,但这只会导致不同的错误,并且我也不指望需要这个。
为什么“AsMutPtr”特征不起作用?
4个回答

5

不幸的是,为&mut T而不是&T编写特质实现确实有很大的区别。相对于&T&mut T不是Copy,因此您无法直接从共享引用中提取它:

& &T      --->  &T
& &mut T  -/->  &mut T

这是相当自然的 - 否则可变引用的别名将成为可能,这违反了Rust借用规则。
你可能会问外部的&来自哪里。它实际上来自as_mut_ptr()方法中的&self。如果你对某个东西有一个不可变的引用,即使那个东西内部包含可变引用,你也无法使用它们来改变它们背后的数据。这也将违反借用语义。
不幸的是,我没有看到不使用unsafe的方法。你需要通过值来获取&mut T以将其转换为*mut T,但你无法通过共享引用获取它。因此,我建议你使用ptr::read()
use std::ptr;

impl<'a, T> AsMutPtr<T> for Option<&'a mut T> {
    fn as_mut_ptr(&self) -> *mut T {
        match *self {
            Some(ref val) => unsafe { ptr::read(val) as *mut _ },
            None => ptr::null_mut(),
        }
    }
}
val在此处是&&mut T,因为模式中使用了ref限定符,因此ptr::read(val)返回&&mut T,即使得可变引用别名化。如果它立即转换为原始指针且不泄露,我认为这没问题,但即使结果是原始指针,仍意味着您有两个别名化的可变指针。您应该非常小心地处理它们。

或者,您可以修改AsMutPtr::as_mut_ptr()以按值消耗其目标:

trait AsMutPtr<T> {
    fn as_mut_ptr(self) -> *mut T;
}

impl<'a, T> AsMutPtr<T> for Option<&'a mut T> {
    fn as_mut_ptr(self) -> *mut T {
        match self {
            Some(value) => value as *mut T,
            None => ptr::null_mut()
        }
    }
}

然而,在这种情况下,Option<&mut T> 将被 as_mut_ptr() 消耗。如果,例如,这个 Option<&mut T> 存储在一个结构中,这可能是不可行的。我不确定是否可以手动执行 Option<&mut T> 的重新借用,而不仅仅是 &mut T(它不会自动触发);如果可以的话,按值传递的 as_mut_ptr() 可能是最好的整体解决方案。


谢谢您的解释 - 在我完全理解借用检查器之前,我还有很长的路要走,这对我很有帮助。 - Brian
"我认为如果它立即转换为原始指针并且不泄漏,那么这是可以的" → 我不太确定,同时拥有两个活动别名&mut似乎是非法的。我听说的规则是,您永远不应该混合使用*&,以免陷入这种情况。 - Veedrac

2
问题在于您正在从一个&中读取一个&mut,但是&mut不是Copy类型,因此必须移动 - 但您无法从常量引用中移动。这实际上解释了Vladimir Matveev关于&&mut → &的洞见,涉及到更基本的属性。
这个问题其实相对简单解决。如果您可以读取*const _,那么您也可以读取*mut _。两者是相同的类型,除了一个标志,表示“要小心,此处正在共享”。由于无论哪种方式,解引用都是不安全的,因此实际上没有理由阻止您在两者之间进行转换。
因此,您实际上可以执行以下操作:
match *self {
    Some(ref val) => val as *const _ as *mut _,
    None => ptr::null_mut(),
}

阅读不可变引用,将其转换为不可变指针,然后再转换为可变指针。而且这一切都是通过安全的Rust完成的,因此我们知道我们没有违反任何别名规则。

话虽如此,直到&mut引用消失之前,实际使用该*mut指针可能是一个非常糟糕的想法。我会非常谨慎地处理这个问题,并尝试重新思考您的包装器以实现更安全的方式。


0

这个会按照你的期望工作吗?

trait AsMutPtr<T> {
    fn as_mut_ptr(self) -> *mut T;
}

impl<T> AsMutPtr<T> for Option<*mut T> {
    fn as_mut_ptr(self) -> *mut T {
        match self {
            Some(val) => val as *mut _,
            None => ptr::null_mut(),
        }
    }
}

它来自于 Option<&mut T> 而不是 Option<*mut T>;你很接近了! - Matthieu M.

0
为了避免不安全的代码,将特性更改为接受 &mut self 而不是 self&self
trait AsMutPtr<T> {
    fn as_mut_ptr(&mut self) -> *mut T;
}

impl<'a, T> AsMutPtr<T> for Option<&'a mut T> {
    fn as_mut_ptr(&mut self) -> *mut T {
        match self {
            Some(v) => *v,
            None => ptr::null_mut(),
        }
    }
}

如果你愿意的话,你也可以将实现简化为一行代码:

fn as_mut_ptr(&mut self) -> *mut T {
    self.as_mut().map_or_else(ptr::null_mut, |v| *v)
}

此方法可以从相同来源获取多个可变原始指针。但这很容易导致可变别名,所以请小心使用:
fn example(mut v: Option<&mut u8>) {
    let b = v.as_mut_ptr();
    let a = v.as_mut_ptr();
}

我建议不要将不可变的引用转换为可变指针,因为这很可能会导致未定义的行为。

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