How to wrap a borrowed value in a newtype that is also a borrowed value?

。_饼干妹妹 提交于 2020-12-21 02:54:45

问题


I am trying to use the newtype pattern to wrap a pre-existing type. That inner type has a modify method which lets us work with a borrowed mutable value in a callback:

struct Val;

struct Inner(Val);

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

Now I want to provide a very similar method on my newtype Outer, which however should not work on Vals but again a newtype wrapper 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(/* ??? */));
    }
}

This code is a reduced example from the original API. I don't know why the reference is returned from the closure, maybe to facilitate chaining, but it shouldn't be necessary. It takes &self because it uses internal mutability - it's a type representing a peripheral register on an embedded system

How do I get a &mut WrappedVal from a &mut Val?

I have tried various things, but all were busted by the borrow-checker. I cannot move the Val out of the mutable reference to construct a proper WrappedVal, and I couldn't get lifetimes to compile either when experimenting around with struct WrappedVal(&'? mut Val) (which I don't really want actually, since they are complicating a trait implementation).

I eventually got it to compile (see Rust playground demo) using the absolute horror of

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()
});

but surely there must be a better way?


回答1:


There is no safe way with your current definition, and your unsafe code is not guaranteed to be safe. There's no contract that the layout of a WrappedVal matches that of a Val, even though that's all it holds.

Solution not using unsafe

Don't do it. Instead, wrap the reference:

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)
    }
}

Solution using unsafe

You can state that your type has the same representation as the type it wraps, making the pointers compatible via 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) }
        })
    }
}

With repr(transparent) in place, the two pointers are interchangable. I ran a quick test with Miri and your full example and didn't receive any errors, but that's not a silver bullet that I didn't mess something else up.




回答2:


Using the ref_cast library you can write:

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

Then you can convert using WrappedVal::ref_cast_mut(v).



来源:https://stackoverflow.com/questions/53999600/how-to-wrap-a-borrowed-value-in-a-newtype-that-is-also-a-borrowed-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!