rust-lang/rfcs

Some way to simulate `&mut` reborrows in user code

nikomatsakis opened this issue · 17 comments

@carllerche wanted a way to make a newtyped &mut that would reborrow in the same implicit way that reborrows do today. That is, with an &mut, foo(ptr) is roughly equivalent to foo(&mut *ptr), but if ptr is MyRef<'a>(&'a mut ...), then this will not coerce from MyRef<'a> to MyRef<'b> (where 'a:'b).

In one of the designs for rust-cpython that I explored; I ran into a similar issue:
I had a zero-sized struct (newtype around PhantomData<&mut 'p ()>), which could not be Copy (unsafe code relied on the fact that user code had access to at most one instance of the struct at a time).
But this struct still had to be passed around between function calls; which needs something like re-borrowing to avoid the code becoming unwieldy.

In my case, I ended up going with a different design where the struct could be Copy instead. (although there were more reasons for that than the lack of reborrowing)

+1. Commenting to track.

eddyb commented

We could handle this similarly to unsizing coercions - see Rc's CoerceUnsized impl, which "only" require that it has a single field also implementing CoerceUnsized, and this repeats all the way down to the primitive impls (specified in #982).

So if we generalized, impl<T: ?Sized, U: ?Sized> Coerce<Rc<U>> for Rc<T> would require that *mut T: Coerce<*mut U> which can be satisfied with an T: Unsize<U> bound.

And impl<'a, 'b> Coerce<MyRef<'b>> for MyRef<'a> would require 'a: 'b and borrowck would treat it as a reborrow.

Should it work on multiple fields, including mixed coercions, e.g. one unsizing and one reborrowing, in different fields?

FWIW, we currently kind of support reborrowing combined with unsizing due to the way the CoerceUnsized impls for &T and &mut T are generic over both input and output lifetimes, so impl CoerceUnsized<MyRef<'b, U>> for MyRef<'a, T> would work, but borrowck may not understand it as a reborrow.

@eddyb In my case, my type has multiple fields. If I understand your comment, then implementing CoerceUnsized wouldn't work?

eddyb commented

@carllerche implementing CoerceUnsized doesn't work unless you have an unsizing and it doesn't even result in a reborrow.
I'm talking about extending the mechanism to allow all coercions to happen inside user-defined types, with opt-in at the definition site.

Personally, I think Option<&mut T> having reborrows would be quite handy.

Personally, I think Option<&mut T> having reborrows would be quite handy.

Yes, please. In one piece of code that really should have used Option<&mut HashSet<Name>> I’ve ended up using &mut Option<HashSet<Name>> in order to get re-borrows.

cc me

This is sort of possible today. Consider the following (messy, off-the-top-of-my-head, borderline embarrassing) associated-type-encoded-HKTs-based formulation:

pub trait Reborrowable<'a> {
    type Result;
}
pub trait ReborrowMut<'self_>: for<'a> Reborrowable<'a> + Reborrowable<'self_, Result=Self> {
    fn reborrow<'reborrow>(&'reborrow mut self)
        -> <Self as Reborrowable<'reborrow>>::Result
        where 'self_: 'reborrow;
}

This can be used as so: https://gist.github.com/anonymous/4aa1bfddaf5efdb53b15 (sans the type-identity trait bound to ensure Reborrowable is well-behaved; I believe that particular bit not working is merely a bug rather than a missing feature).

I'm not saying that this is a solution - far from it. Still, it's possible to within a single function call on a trait (which is just about what the Deref trait does with autoderef'ing).

sgrif commented

Ran into a need for this today.

I've run into this twice now.

Edit: better example
Also see: rust-random/rand#287

This would also be super helpful for iovec which is used heavily as part of Mio / Tokio / the bytes crate, etc... for vectored operations.

Ran into it too. It's really annoying since the workarounds are very verbose and hard to understand.

I've also run into it.

Is there a reason why a simple Reborrow marker trait would not work? For compiler it can behave as Copy, but with additional restriction on having two copies at the same time.

Not to bikeshed, but what about (an incredibly verbose) &re T & &re mut T?

@newpavlov I would honestly recommend just using a reborrow method for now, like this one:

impl<'a, DS: DrawSharedImpl> DrawIface<'a, DS> {
    /// Reborrow with a new lifetime
    pub fn reborrow<'b>(&'b mut self) -> DrawIface<'b, DS>
    where
        'a: 'b,
    {
        DrawIface {
            draw: &mut *self.draw,
            shared: &mut *self.shared,
            pass: self.pass,
        }
    }
}

Having an official trait for this and having it used automatically might be nice but isn't a big deal.

BTW we ran into it years ago with the Rand project (something to do with passing R: Rng where &mut Rng impls Rng I think, but don't remember the details).

@ShadowJonathan I don't see the point of special syntax. As far as I'm concerned the point is to make the usual syntax for reborrows work for "reference wrappers" so that users don't have to think about it.

If it were possible to write custom impls for the Pointee

This is custom DSTs. I believe there’s a lot more language design involved than "just allow Pointee impls". Multiple RFCs have attempted to define this already, as you probably know.

Please let’s keep this issue about re-borrowing of existing types like Option<&mut Foo>. If and when custom DSTs are added, whatever is done here should apply too.