Unconditional recursion warning for converting mut into const in `Deref`

I have the following code

struct Foo<B> {
    index: (usize, usize), // just an example
    buf: B
}

I want to write methods that will work on both B = &Bar and B = &mut Bar (again for the sake of example). So I have the following Deref impl:

impl<'a> Deref for Foo<&'a mut Bar> {
    type Target = Foo<&'a Bar>;
    fn deref(&self) -> &Self::Target {
        self
    }
}

I get a warning about unconditional recursion, but to me it doesn't recurse - a Foo<&Bar> isn't goint to magically get promoted to a Foo<&mut Bar>, so the function won't recurse. It also seems to work well in practice. Am I missing something??

Update: it actually blows the stack. But I don't see why this should recurse.

UPDATE 2
It works fine when I transmute:

impl<'a> Deref for Foo<&'a mut Bar> {
    type Target = Foo<&'a Bar>;
    fn deref(&self) -> &Self::Target {
        unsafe { mem::transmute(self) }
    }
}

But I'd obviously rather not use unsafe if I can avoid it.

Compiler won't coerce &mut T to &T if it's a part of another type. You'd have to destructure your Foo and put it back together, letting compiler see "naked" reference.

Edit:
This is nonsense. If this was true, the first example wouldn't even compile.

Edit2:
Oh, got it.
In first example compiler tries to apply deref coersion to self, causing infinite recursion.

1 Like

Why does this code compile? You are expected to return a value of type &Self::Target, i.e. &Foo<&Bar>, but self is of type &Foo<&mut Bar>. This would result in a compilation error for mismatching types, unless the compiler can find a coercion to make it work. The coercion it does find is deref coercion:

In Rust, a type &T can coerce into &S if T implements Deref<Target = S>.

This coercion is implemented by calling said Deref implementation, in this case it’s the very Deref implementation we’re just implementing, hence it’s recursive. It’s like defining a conversion function such as

fn u8_to_string(x: u8) -> String {
    u8_to_string(x)
}

by calling itself. It doesn’t work. Instead, you’ll have to provide an actual definition.


In this case, however, there is no proper definition in the first place! Whilst converting Foo<&mut Bar> into Foo<&Bar> would be possible (just deconstruct the Foo, get the reference, cast it, construct a new Foo value, done…), it cannot be done behind a reference.

There’s no way to implement this Deref impl in safe Rust, and your unsafe solution isn’t proper either. You’re essentially implementing a &'a &'b mut T -> &'a &'b T conversion, which would then allow users to obtain a long-lived &'b T from a short re-borrow &'a &'b mut T of a mutable reference, which is unsound, because it can be used to create an aliased mutable reference. Specifying the lifetimes in a sound way isn’t supported by the way that the Deref trait works, either, because the &self borrow’s lifetime cannot appear in the Target type.

Of course, for the original goal of supporting both B = &Bar and B = &mut Bar for some method, that’s still achievable using other means. If you were to share with us what the intended use-case looks like, we could certainly suggest approaches that don’t involve unsound Deref implementations.

8 Likes

I don't get what Foo<&Bar> has anything to do with this. That's only the Target type.

The very fact that you have self: &Foo<&mut Bar> and you use it to return a &Foo<&Bar> means by definition that you are invoking a Deref coercion – which is the very function you are trying to implement.

1 Like

@steffahn Thanks for the detailed answer! I think I assumed that the & would re-borrow the &mut, but because there's a transmute the compiler doesn't know that.

I can't share the exact code I'm working on, but essentially Bar is a large buffer type, and Foo is a reference type over Bar that also containes indices into the buffer. The other approaches I'd considered were duplicating methods using a macro (which I'd prefer not to do because compile size is an issue for us), and impl<B> Foo<B> where B: Deref<Target = Bar> but when I used this strategy some of the more complex methods got a bit hairy and again I'm worried about monomorphisation causing code bloat when I know that it isn't necessary.

Are there any other methods I could use that I've overlooked?

Regarding binary size… there’s probably a good chance that identical code between Foo<&Bar> and Foo<&mut Bar> might get deduplicated anyways, if you take an approach such as e.g. the impl<B> Foo<B> where B: Deref<Target = Bar> one. Though I might be expecting too much from the optimizer here, I’m not entirely certain; one would have to inspect the assembly :slight_smile:

Another approach would be to try implementing the Foo<&mut Bar> methods in terms of the Foo<&Bar> ones using some kind of shim. E.g. a function like

impl Foo<&mut Bar> {
    fn reborrow_immutable(&self) -> Foo<&Bar> {
        Foo {
            index: self.index,
            buf: &*self.buf,
        }
    }
}

would allow a &self method to be implemented like

impl Foo<&Bar> {
    fn foo(&self) { …long code… }
}
impl Foo<&mut Bar> {
    fn foo(&self) {
        self.reborrow_immutable().foo()
    }
}

so that if you have - say - a macro generate code like this, it’s an even better chance to avoid code bloat.

Of course, for &mut self methods, e.g. mutating the indices, this doesn’t quite work… you could perhaps have something with a callback :thinking:

impl Foo<&mut Bar> {
    fn with_reborrow_immutable<R>(&mut self, f: impl FnOnce(&mut Foo<&Bar>) -> R) -> R {
        let mut foo = Foo {
            index: self.index,
            buf: &*self.buf,
        };
        let r = f(&mut foo);
        self.index = foo.index;
        r
    }
}
impl Foo<&Bar> {
    fn foo(&mut self) { …long code… }
}
impl Foo<&mut Bar> {
    fn foo(&mut self) {
        self.with_reborrow_immutable(|this| this.foo())
    }
}

though this does involve some additional code to write back the modifications to the index… maybe even better could be an approach that does de-duplicate using a helper function. E.g. your methods could be written like this

impl<B> Foo<B> where B: Deref<Target = Bar> {
    fn foo(&mut self, s: String) -> Result<(), ()> {
        fn foo(index: &mut (usize, usize), buf: &Bar, s: String) -> Result<(), ()> {
            // …long code here…
            Ok(())
        }
        foo(&mut self.index, &*self.buf, s)
    }
}

with the effect that the inner foo isn’t monomorphized for different B to begin with. (This style of code could be macro-generated, too.)

1 Like