lovasoa
lovasoa

Reputation: 6855

Why can't a boxed struct be borrowed as a trait?

Given a struct S implementing a trait T, why doesn't Box<S> implement Borrow<dyn T>?

The following code, that I would have expected to compile, doesn't:

trait T{}
struct S{}
impl T for S{}

fn f1(s: &S) -> &dyn T {
    s
}

fn f2(s: &Box<S>) -> &dyn T {
    std::borrow::Borrow::borrow(s)
}

Why does f1 compile while f2 doesn't? (The conversion from &S to &dyn T is done in the first case and not in the second).

Upvotes: 4

Views: 457

Answers (2)

Peter Hall
Peter Hall

Reputation: 58835

This is to do with the way that type inference and type coercion work. The Borrow<B> trait's parameter is the type of the borrowed value, and the type checker needs to know what it is.

If you just write:

std::borrow::Borrow::borrow(s)

Then the type B in Borrow<B> will be inferred from the surrounding code. In your case it is inferred to be dyn T because that's the return value. However, dyn T is a completely different type from S, so it doesn't type-check.

Once the type checker knows that the value being returned is of type &S then it can coerce it to a &dyn T, but you need to give it that information:

fn f2(s: &Box<S>) -> &dyn T {
    let s: &S = std::borrow::Borrow::borrow(s);
    s
}

Or, more concisely:

fn f2(s: &Box<S>) -> &dyn T {
    std::borrow::Borrow::<S>::borrow(s)
}

The reason why Sébastien Renauld's answer works is because Deref uses an associated type instead of a type parameter. The type-checker can easily infer the <S as Deref>::Target because there can only be one implementation of Deref per type and the associated Target type is uniquely determined. Borrow is different because Box<S> could implement Borrow<()>, Borrow<i32>, Borrow<Box<Option<Vec<bool>>>>,... so you have to be more explicit about which implementation you intend.

Upvotes: 4

S&#233;bastien Renauld
S&#233;bastien Renauld

Reputation: 19672

&Box<S> is not directly equal to Box<&S>, and this is why it does not compile directly.

You can relatively easily fix this by dereferencing, like so:

use std::ops::Deref;
trait T{}
struct S{}
impl T for S{}

fn f1(s : &S) -> &(dyn T) {
    s
}

fn f2(s : &Box<S>) -> &(dyn T) {
    s.deref()
}

(The trait Deref is there for slightly easier readability)

The call to deref() operates over &self, so having &Box<S> is sufficient to call it. It simply returns &S, and since that implements T the types check out.

Upvotes: 1

Related Questions