我正在围绕C库编写一个Rust包装器,同时这样做我试图利用&#34;可空指针优化&#34;在The Book中提到过,但我找不到将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
特质不起作用?
答案 0 :(得分:4)
不幸的是,为&mut T
而不是&T
编写特征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
此处为& &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()
可能是最好的整体解决方案。
答案 1 :(得分:2)
问题是您正在阅读&mut
中的&
,但&mut
不是Copy
所以必须移动 - 而且您无法移动超出const引用。这实际上解释了Vladimir Matveev在更基本属性方面对&&mut → &
的洞察力。
这实际上相对简单地解决了。如果您可以阅读*const _
,则可以阅读*mut _
。这两个是相同的类型,标志着“小心,这是共享的”。由于解除引用无论如何都是不安全的,实际上没有理由阻止你在两者之间施放。
所以你实际上可以做到
match *self {
Some(ref val) => val as *const _ as *mut _,
None => ptr::null_mut(),
}
读取不可变引用,使其成为不可变指针,然后使其成为可变指针。此外,它都通过安全的Rust完成,因此我们知道我们没有违反任何别名规则。
也就是说,在*mut
引用消失之前实际使用&mut
指针可能是一个非常糟糕的主意。我会对此非常犹豫,并试图重新考虑你的包装更安全。
答案 2 :(得分: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(),
}
}
}
答案 3 :(得分:0)
要避免使用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)
}
这可用于为您提供来自同一源的多个可变原始指针。这很容易导致您导致可变的 aliasing ,因此请注意:
fn example(mut v: Option<&mut u8>) {
let b = v.as_mut_ptr();
let a = v.as_mut_ptr();
}
我建议不要将 immutable 引用转换为 mutable 指针,因为非常可能会导致不确定的行为。