【问题标题】:Convert Option<&mut T> to *mut T将 Option<&mut T> 转换为 *mut T
【发布时间】:2016-03-09 07:39:46
【问题描述】:

我正在围绕 C 库编写 Rust 包装器,同时我试图利用 The Book 中提到的“可空指针优化”,但我找不到转换 @ 的好方法987654322@ 到 *const TOption&lt;&amp;mut T&gt;*mut T 就像他们所描述的那样。

我真正想要的是能够打电话给Some(&amp;foo) as *const _。不幸的是,这不起作用,所以我能想到的下一个最好的事情是Option&lt;T&gt; 的一个特征,它使我能够调用Some(&amp;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(&amp;foo).as_ptr() 来获得*const _,我希望能够致电Some(&amp;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 trait 无法编译。当我尝试时,我收到以下错误:

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 特征不起作用?

【问题讨论】:

    标签: pointers rust ffi


    【解决方案1】:

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

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

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

    你可能会问那个外部的&amp; 是从哪里来的。它实际上来自as_mut_ptr() 方法中的&amp;self。如果您有对某事物的不可变引用,即使该事物内部包含可变引用,您也无法使用它们来改变它们背后的数据。这也违反了借用语义。

    不幸的是,我认为没有不安全的方法可以做到这一点。您需要拥有&amp;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 这里是&amp; &amp;mut T,因为模式中有ref 限定符,因此ptr::read(val) 返回&amp;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&lt;&amp;mut T&gt; 将被 as_mut_ptr() 消耗。例如,如果 Option&lt;&amp;mut T&gt; 存储在结构中,这可能不可行。我不确定是否可以使用Option&lt;&amp;mut T&gt; 而不是&amp;mut T 手动执行重新借用(它不会自动触发);如果可能,那么按值as_mut_ptr() 可能是最好的整体解决方案。

    【讨论】:

    • 感谢您的解释——在我完全理解借用检查器之前还有很长的路要走,这很有帮助。
    • “我认为如果它立即转换为原始指针并且不泄漏就可以了” → 我不太确定,有两个活动别名 &amp;muts 肯定看起来是非法的。我听说的规则是,你永远不应该把*&amp; 混在一起,以免你最终陷入这种情况。
    【解决方案2】:

    问题是您正在从&amp; 中读取&amp;mut,但&amp;muts 不是Copy,因此必须移动 - 而且您不能移出 const 引用。这实际上解释了 Vladimir Matveev 对&amp;&amp;mut → &amp;s 的更基本属性的见解。

    这其实是比较简单的解决了。如果您可以阅读*const _,您可以阅读*mut _。两者是同一类型,竖起一个写着“小心,这是共享的”的标志。由于取消引用无论哪种方式都是不安全的,因此实际上没有理由阻止您在两者之间进行转换。

    所以你实际上可以做到

    match *self {
        Some(ref val) => val as *const _ as *mut _,
        None => ptr::null_mut(),
    }
    

    读取不可变引用,使其成为不可变指针,然后使其成为可变指针。另外,这一切都是通过安全的 Rust 完成的,所以我们知道我们没有违反任何别名规则。

    也就是说,在 &amp;mut 引用消失之前实际使用 *mut 指针可能是一个非常糟糕的主意。我会对此犹豫不决,并尝试将您的包装器重新考虑为更安全的东西。

    【讨论】:

      【解决方案3】:

      这会达到你的预期吗?

      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&lt;&amp;mut T&gt; 而不是Option&lt;*mut T&gt;;你已经接近了!
      【解决方案4】:

      要避免unsafe 代码,请将特征更改为接受&amp;mut self 而不是self&amp;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 指针,因为这非常可能导致未定义的行为。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-11-23
        • 1970-01-01
        • 1970-01-01
        • 2023-01-03
        • 1970-01-01
        • 2019-05-23
        • 1970-01-01
        相关资源
        最近更新 更多