将选项<和mut T>转换为*mut T



我正在围绕 C 库编写一个 Rust 包装器,在这样做的同时,我试图利用书中提到的"可为空指针优化",但我找不到一种好方法将Option<&T>转换为*const T并将Option<&mut T>转换为他们所描述的*mut T

我真正想要的是能够打电话给Some(&foo) as *const _.不幸的是,这不起作用,所以我能想到的下一个最好的事情是Option<T>上的一个特质,它使我能够打电话给Some(&foo).as_ptr()。以下代码是该特征的工作定义和实现:

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性状不起作用?

不幸的是,为 &mut T 而不是&T编写 trait impl 确实有很大的不同&mut T&T 相反,不是Copy的,因此您不能直接从共享引用中提取它:

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

这是相当自然的 - 否则可变引用的别名将是可能的,这违反了 Rust 借用规则。

你可能会问,这种外在&从何而来。它实际上来自&self as_mut_ptr()方法。如果你有一个对某物的不可变引用,即使该某物包含可变引用,你也无法使用它们来改变它们背后的数据。这也违反了借用语义。

不幸的是,我认为没有不安全就无法做到这一点。你需要有"按值"&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由于模式中的限定符ref & &mut T,因此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()可能是最好的整体解决方案。

问题是您正在从&中读取&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指针可能是一个非常糟糕的主意。我会对此非常犹豫,并尝试将您的包装重新考虑为更安全的东西。

这会达到您的预期吗?

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(),
        }
    }
}

为了避免unsafe代码,请将特征更改为接受&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();
}

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

最新更新