将 Option<&mut T> 转换为 *mut T
Convert Option<&mut T> to *mut T
我正在围绕 C 库编写一个 Rust 包装器,同时我试图利用 The Book 中提到的 "nullable pointer optimization",但我找不到一个好的将 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
特性不起作用?
这会达到您的预期吗?
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(),
}
}
}
不幸的是,为 &mut T
而不是 &T
编写 trait impl 确实 有很大的不同。 &mut T
,与 &T
相反,不是 Copy
,因此您不能直接从共享引用中提取它:
& &T ---> &T
& &mut T -/-> &mut T
这是很自然的——否则可变引用的别名是可能的,这违反了 Rust 借用规则。
你可能会问那个外&
是从哪里来的。它实际上来自as_mut_ptr()
方法中的&self
。如果您有对某物的不可变引用,即使该某物内部包含可变引用,您也无法使用它们来改变它们背后的数据。这也违反了借用语义。
不幸的是,如果没有不安全,我看不出有什么办法可以做到这一点。您需要 &mut T
"by value" 才能将其转换为 *mut T
,但您无法通过共享引用获取它 "by value"。因此,我建议您使用 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)
returns &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
,因此必须移动 - 您可以' t 移出 const 引用。这实际上解释了 Vladimir Matveev 关于 &&mut → &
s 在更基本属性方面的见解。
这个其实比较简单就解决了。如果你能读一个*const _
,你就能读一个*mut _
。两者是同一类型,禁止显示 "be careful, this is being shared" 的标志。由于取消引用无论哪种方式都不安全,实际上没有理由阻止您在两者之间转换。
所以你实际上可以做到
match *self {
Some(ref val) => val as *const _ as *mut _,
None => ptr::null_mut(),
}
读取不可变引用,使其成为不可变指针,然后使其成为可变指针。此外,这一切都是通过安全的 Rust 完成的,所以我们知道我们没有违反任何别名规则。
也就是说,在 &mut
引用消失之前实际使用 *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();
}
我建议不要将 immutable 引用转换为 mutable 指针,因为这是 very 可能会导致未定义的行为。
我正在围绕 C 库编写一个 Rust 包装器,同时我试图利用 The Book 中提到的 "nullable pointer optimization",但我找不到一个好的将 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
特性不起作用?
这会达到您的预期吗?
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(),
}
}
}
不幸的是,为 &mut T
而不是 &T
编写 trait impl 确实 有很大的不同。 &mut T
,与 &T
相反,不是 Copy
,因此您不能直接从共享引用中提取它:
& &T ---> &T
& &mut T -/-> &mut T
这是很自然的——否则可变引用的别名是可能的,这违反了 Rust 借用规则。
你可能会问那个外&
是从哪里来的。它实际上来自as_mut_ptr()
方法中的&self
。如果您有对某物的不可变引用,即使该某物内部包含可变引用,您也无法使用它们来改变它们背后的数据。这也违反了借用语义。
不幸的是,如果没有不安全,我看不出有什么办法可以做到这一点。您需要 &mut T
"by value" 才能将其转换为 *mut T
,但您无法通过共享引用获取它 "by value"。因此,我建议您使用 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)
returns &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
,因此必须移动 - 您可以' t 移出 const 引用。这实际上解释了 Vladimir Matveev 关于 &&mut → &
s 在更基本属性方面的见解。
这个其实比较简单就解决了。如果你能读一个*const _
,你就能读一个*mut _
。两者是同一类型,禁止显示 "be careful, this is being shared" 的标志。由于取消引用无论哪种方式都不安全,实际上没有理由阻止您在两者之间转换。
所以你实际上可以做到
match *self {
Some(ref val) => val as *const _ as *mut _,
None => ptr::null_mut(),
}
读取不可变引用,使其成为不可变指针,然后使其成为可变指针。此外,这一切都是通过安全的 Rust 完成的,所以我们知道我们没有违反任何别名规则。
也就是说,在 &mut
引用消失之前实际使用 *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();
}
我建议不要将 immutable 引用转换为 mutable 指针,因为这是 very 可能会导致未定义的行为。