Deref coercion inside a wrapper type

Deref coercion works well for plain references. I want to take advantage of that coercion inside a newtype, but I can't seem to figure out how.

(See the end of this post for a playground link)

Let's say we have these types:

struct Foo;
struct Bar(Foo);
struct Quux(Bar);
struct Waldo(Quux);

In all these examples, I'll be trying to transitively coerce a &Waldo all the way down to a &Foo, using these impls:

impl Deref for Waldo { type Target = Quux; }
impl Deref for Quux { type Target = Bar; }
impl Deref for Bar { type Target = Foo; }

Coercing a plain reference works fine:

fn takes_a_foo(_: &Foo) {}

pub fn test() {
    let waldo = Waldo(Quux(Bar(Foo)));
    takes_a_foo(&waldo);
}

Now let's say we want to throw a newtype into the mix. Is there anything I can do to keep the same level of ergonomics?

#[repr(transparent)]
#[derive(Copy, Clone)]
struct RefWrapper<'a, T>(&'a T);

fn takes_a_wrapped_foo(_: RefWrapper<'_, Foo>) {}

pub fn wrapped_test() {
    let waldo = Waldo(Quux(Bar(Foo)));
    takes_a_wrapped_foo(RefWrapper(&waldo)); // <-- ???
}

Here is my closest attempt:

impl<'a, T> RefWrapper<'a, T> {
    fn coerce<Target>(self) -> RefWrapper<'a, Target> where T: Deref<Target = Target> {
        RefWrapper(self.0)
    }
}

fn takes_a_wrapped_foo(_: RefWrapper<'_, Foo>) {}

pub fn wrapped_test() {
    let waldo = Waldo(Quux(Bar(Foo)));
    takes_a_wrapped_foo(RefWrapper(&waldo).coerce().coerce().coerce()); // :(
}

Note the three calls to coerce(). I'm fine with one explicit call, but having to count up the total number of coercions needed at every call site is a bit silly. I think the way to improve on this is to take advantage of coercion transitivity somehow (instead of writing out the where T: Deref bound by hand), but I'm not sure where to go from here.

Here's the full code in the playground. Can anyone point me in the right direction?

Note that there's real Foo exist behind the &Waldo, the value of type RefWrapper<'_, Foo> does not exist at all behind the RefWrapper<'_, Waldo>. Rust doesn't create a new value out of the thin air during the deref coercion.

2 Likes

I get what you're saying, but I'm not sure I agree.

Before the call to takes_a_foo, there exists a Waldo, Foo, and &Waldo, but not &Foo. The compiler creates the &Foo on-demand by following the chain of Deref::deref().

Similarly, before the call to takes_a_wrapped_foo, there exists a Waldo, Foo, and RefWrapper<Waldo>, but not RefWrapper<Foo>. I'd like the compiler to create a RefWrapper<Foo> on-demand by following the chain of RefWrapper::coerce() (or any trait/method, doesn't matter what it's called).

Is there some way to hook a trait bound into that Deref compiler magic?

Nope.

Bummer. Is there any other approach?

I think the closest you can get is by letting the user use Deref coercion:

impl<'a, T> RefWrapper<'a, T> {
    fn map<Target, F>(self, f: F) -> RefWrapper<'a, Target>
    where F: FnOnce(&'a T) -> &'a Target {
        RefWrapper(f(self.0))
    }
}

Then either of the following are possible:

takes_a_wrapped_foo(RefWrapper(&waldo).map(|x| -> &Foo { x }));

takes_a_wrapped_foo(RefWrapper(&waldo).map::<Foo, _>(|x| x));

Notice that some form of type annotation is necessary in the map call. This is so that the compiler knows to insert the coercion at the time that it is type-checking the expression x. (without the annotation, it won't know about the Foo/Waldo mismatch until the time it type-checks the takes_a_wrapped_foo call, and by then it will have decided that the closure produced &Waldo)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.