如何将借入的值包装在也是借入的值的新类型中?

时间:2019-01-01 22:59:57

标签: reference rust borrowing newtype

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

struct Val;

struct Inner(Val);

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

现在,我想在我的新类型Outer上提供一种非常相似的方法,但是该方法不适用于Val,但还是要使用新类型包装器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的简化示例。我不知道为什么从闭包中返回引用,也许是为了方便链接,但这不是必须的。之所以需要&self是因为它使用了内部可变性-它是一种表示嵌入式系统上的外围寄存器的类型

如何从&mut WrappedVal获得&mut Val

我尝试过各种方法,但都被借阅检查器破坏了。我无法将Val从可变引用中移出以构造适当的WrappedVal,并且在尝试struct WrappedVal(&'? 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()
});

但是肯定有更好的方法吗?

2 个答案:

答案 0 :(得分:3)

当前的定义没有安全的方法,也不保证您的不安全代码是安全的。尽管仅此而已,但没有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,但没有收到任何错误,但这并不是我没有弄乱其他东西的灵丹妙药。

答案 1 :(得分:1)

使用ref_cast库,您可以编写:

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

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