Funny interaction between deref coersion and `Vec<Box<Trait>>`

I just encountered a funny edge case with boxed trait objects and automatic deref coersions. Basically, a &Box<Trait> doesn't coerce to &Trait, even when you try to use the trick of dereferencing and immediately borrowing &*thing.

Does anyone know why this doesn't currently work?

pub trait Trait {}

fn uses_trait(t: &Trait) {}

struct Foo;
impl Trait for Foo {}

fn main() {
    let trait_objects: Vec<Box<Trait>> = vec![Box::new(Foo)];

    uses_trait(&trait_objects[0]);  // Error: the trait `Trait` is not implemented for `std::boxed::Box<Trait>`
    
    for t in &trait_objects {
        uses_trait(&*t);  // the deref-reref pattern doesn't work here either
    }
}
2 Likes

I suspect that the unsized coercion from &Box<Trait> to &Trait (which would be possible if Box<Trait>: Trait) took precedence over the deref coercion from &Box<Trait> to &Trait (which is possible because Box<Trait>: Deref<Target=Trait>) here. Since the resolution of the trait bound Box<Trait>: Trait, which is required by the unsized coercion, is delayed, the deref coercion isn't even considered, perhaps.

Looking into coercion algorithm in the compiler, unsized coercion is tried before deref coercion.

2 Likes

This works as expected:

    for t in &trait_objects {
        uses_trait(&**t);  // the deref-reref pattern doesn't work here either
    }

** is required to go from &Box<T> to Box<T> to T.

But I'm surprised I can't deref trait_objects[0] with *. Explicit call does work :open_mouth:

use std::ops::Deref;
uses_trait(trait_objects[0].deref());

OK, this works:

uses_trait(&**&trait_objects[0])

That's a new crying smiley I haven't seen yet: &**&

You don't need that, Index already derefs once: &*trait_objects[0] works.

uses_trait(&**&trait_objects[0])

*& is always redundant, since it invokes the (uninteresting) Deref of &T.

Ah, you're right. I misread the error messages.

I had a feeling &**t would work, but I feel like the whole point of deref coersion was to make sure we don't get the C problem of "adding more *'s until it compiles".

Is the coercion order specified in an RFC anywhere? It's an edge case of an edge case, but I feel like coercion order should be defined somewhere other than in the guts of the compiler.

Deref coercion not working for Box<Trait> #22194
https://github.com/rust-lang/rust/issues/22194

2 Likes