【问题标题】:How to wrap a borrowed value in a newtype that is also a borrowed value?如何将借来的值包装在也是借来的值的新类型中?
【发布时间】:2019-01-01 22:59:56
【问题描述】:

我正在尝试使用newtype pattern 来包装预先存在的类型。该内部类型有一个modify 方法,它允许我们在回调中使用借用的可变值:

struct Val;

struct Inner(Val);

impl Inner {
    fn modify<F>(&self, f: F)
    where F: FnOnce(&mut Val) -> &mut Val { … }
}

现在我想在我的新类型Outer 上提供一个非常相似的方法,但是它不应该在Vals 上工作,但又是一个新类型包装器WrappedVal

struct Outer(Inner);
struct WrappedVal(Val);

impl Outer {
    fn modify<F>(&self, f: F)
    where
        F: FnOnce(&mut WrappedVal) -> &mut WrappedVal,
    {
        self.0.modify(|v| f(/* ??? */));
    }
}

此代码是原始 API 的简化示例。我不知道为什么从闭包返回引用,也许是为了方便链接,但它不应该是必要的。它采用&amp;self,因为它使用内部可变性——它是一种表示嵌入式系统上的外围寄存器的类型

如何从&amp;mut Val 获得&amp;mut WrappedVal

我尝试了各种方法,但都被借用检查器破坏了。我无法将Val 从可变引用中移出以构造正确的WrappedVal,并且在尝试使用struct WrappedVal(&amp;'? mut Val) 时我也无法编译生命周期(实际上我并不真正想要,因为它们是使特征实现复杂化)。

我最终得到了它的编译(见Rust playground demo),使用的绝对恐怖

self.0.modify(|v| unsafe {
    (f((v as *mut Val as *mut WrappedVal).as_mut().unwrap()) as *mut WrappedVal as *mut Val)
        .as_mut()
        .unwrap()
});

但肯定有更好的方法吗?

【问题讨论】:

    标签: reference rust borrowing newtype


    【解决方案1】:

    您当前的定义没有安全的方法,您的不安全代码也不能保证是安全的。没有约定 WrappedVal 的布局与 Val 的布局相匹配,尽管这就是它的全部内容。

    不使用unsafe的解决方案

    不要这样做。相反,包装参考:

    struct WrappedVal<'a>(&'a mut Val);
    
    impl Outer {
        fn modify<F>(&self, f: F)
        where
            F: FnOnce(WrappedVal) -> WrappedVal,
        {
            self.0.modify(|v| f(WrappedVal(v)).0)
        }
    }
    

    使用unsafe的解决方案

    您可以声明您的类型与它所包装的类型具有相同的表示,从而通过repr(transparent) 使指针兼容:

    #[repr(transparent)]
    struct WrappedVal(given::Val);
    
    impl Outer {
        fn modify<F>(&self, f: F)
        where
            F: FnOnce(&mut WrappedVal) -> &mut WrappedVal,
        {
            self.0.modify(|v| {
                // Insert documentation why **you** think this is safe
                // instead of copy-pasting from Stack Overflow
                let wv = unsafe { &mut *(v as *mut given::Val as *mut WrappedVal) };
                let wv = f(wv);
                unsafe { &mut *(wv as *mut WrappedVal as *mut given::Val) }
            })
        }
    }
    

    有了repr(transparent),这两个指针就可以互换了。我跑了a quick test with Miri and your full example 并没有收到任何错误,但这不是我没有搞砸其他事情的灵丹妙药。

    【讨论】:

    • 感谢您提及repr(transparent),我不知道。我应该如何争辩说强制转换是安全的 - 如果值具有相同的内存表示,我应该能够在同一内存位置调用两种类型的方法?我需要评估任何其他方面吗?
    • 我尝试了安全的解决方案,其中 &amp;mut Val 被包裹,但正如问题 I couldn't get the lifetimes right 中所暗示的那样。我不想在我的代码(尤其是Outer)中乱扔只有在WrappedVal 的一种方法中本地需要的生命周期参数,我是不是搞错了?我已经很惊讶你可以省略 FnOnce(WrappedVal) -&gt; WrappedVal 中的生命周期参数。
    • @Bergi 是的,在 trait 中使用这样的生命周期超出了 Rust 当前可以表达的范围; generic associated types (GATs) 需要先实现。由于其灵活性,特征比普通函数更复杂。
    • @Bergi 我在代码中的评论是为了劝阻未来的人不要仅仅复制粘贴代码而不阅读其余的答案。我对代码安全的信念来自于 repr(transparent) 的使用,所以这就是我在评论中使用的,是的。
    【解决方案2】:

    使用ref_cast 库,您可以编写:

    #[derive(RefCast)]
    #[repr(transparent)]
    struct WrappedVal(Val);
    

    然后你可以使用WrappedVal::ref_cast_mut(v)进行转换。

    【讨论】:

      猜你喜欢
      • 2020-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多