Casting &Box<dyn Trait> to &dyn Trait

Could someone help to understand why this code triggers E2077?

error[E0277]: the trait bound `std::boxed::Box<dyn Service>: HelloService` is not satisfied
  --> src/main.rs:25:31
   |
25 |     let s_ref: &dyn Service = &s;
   |                               ^^ the trait `HelloService` is not implemented for `std::boxed::Box<dyn Service>`
   |
   = note: required because of the requirements on the impl of `Service` for `std::boxed::Box<dyn Service>`
   = note: required for the cast to the object type `dyn Service`
trait Service {
    fn print(&self);
}

trait HelloService {
    fn get_message(&self) -> &str {
        "Hello world!"
    }
}

impl<T> Service for T 
where T: HelloService
{
    fn print(&self) {
        println!("{}", self.get_message());
    }
}

struct Hello;

impl HelloService for Hello {}

fn main() {
    let s: Box<dyn Service> = Box::new(Hello);
    let s_ref: &dyn Service = &s;
}

Thanks.

1 Like

Try this:

let s_ref: &dyn Service = &*s;
4 Likes

To expand a little on @jethrogb's answer: The trick here is that Box implements Deref<Target = T>.
In your case @fluxxu, the T type param becomes the Service trait, and thus *s will deref to a dynamically-sized type (DST) that implements Service. Therefore, &*s has type &Service which is the same as &dyn Service.

In contrast, &s directly means borrow s and thus it has type &Box<dyn Service>.

Why doesn't autoderef handle this case, though?

2 Likes

I think autoderef only works its magic in very specific code patterns, e.g. when calling a method on a receiver, the receiver is repeatedly autoderef'ed until the right type for the method has been found.

TBH it's the only place that ATM I can with certainty say that autoderef works there.

Other than that I'm unsure if e.g. adding autoderef to the RHS of a let-stmt would cause issues for other Rust code in the wild. In this case it seems pretty clear what the intent of the author is, but the general case is much less clear.
However, I'm fairly certain that adding it to whatever expr happens to start with a & would be a costly mistake in terms of ergonomicity due to the ambiguity it could cause i.e. does &s mean &Box<dyn Service> or does it mean &dyn Service?
At the very least &*s is unambiguous in intent.

1 Like

I think this is kind of a loophole in how coercions are implemented. The compiler is capable of coercing &Box<dyn Service> to &dyn Service via deref coercion, but only if it doesn't first try to coerce it via DST coercion. You can force this by writing a generic function to do the coercion:

fn coerce<S: ?Sized>(r: &Box<S>) -> &S {
    r
}
let s: Box<dyn Service> = Box::new(Hello);
let s_ref: &dyn Service = coerce(&s);

Just changing the signature of coerce to fn coerce(r: &Box<dyn Service>) -> &dyn Service causes it to fail again.

So I think this is a quirk of the order in which the compiler tries different coercions. Bug, maybe?

There’s a bit of elaboration on this by @qnighy there (it’s indeed due to order of coercion attempts).

2 Likes

Thank you all very much.

Do you think the error message generated

the trait `HelloService` is not implemented for `std::boxed::Box<dyn Service>`

is a bug?
I think it's misleading.
If rustc cannot coerce &Box<dyn Service> to &dyn Service, why does it mention HelloService?

It's not a bug per sé. It's trying to apply your impl<T> Service for T where T: HelloService. You explicitly want something that's dyn Service, so the compiler goes and sees if Box<dyn Service> implements it. It doesn't find any implmentation, but it does find the impl<T> Service for T where T: HelloService. So the compiler says hey if you'd implement HelloService for Box<dyn Service>, everything would be ok here.

1 Like