Creating reference of box

Hey all I am trying to create a reference of a box and I am facing some issue.

Here is a minimal reproducible example

trait A {
    fn smth(&self);
}

struct B {}

impl A for B {
    fn smth(&self) {}
}

fn f(_: &Box<dyn A>) {}


fn main() {

    f(&Box::new( B {}));

}

error[E0308]: mismatched types
  --> src/test.rs:16:7
   |
16 |     f(&Box::new( B {}));
   |       ^^^^^^^^^^^^^^^^ expected trait object `dyn A`, found struct `B`
   |
   = note: expected reference `&Box<(dyn A + 'static)>`
              found reference `&Box<B>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `belote_libre_backend` due to previous error
warning: build failed, waiting for other jobs to finish...
error: build failed

What is wrong in my code ?

More generally how am I supposed to create a reference to a box object ?

thanks in advance :hugs:

You'd need a cast from Box<B> to Box<dyn A>. So you could do something like: &(Box::new(B {}) as Box<dyn A>) or leave it upto type inference by doing &(Box::new(B {}) as _).
As always in Rust, explicit is better than implicit, and that's why you need the explicit cast.

3 Likes

It's a hole in coercion (the RFC and reference say it should work by my readings). And oddly enough, this does:

    f(&{ Box::new( B {}) });
    // ^^     added     ^^

But it's probably less confusing to others to use the cast that was suggested.

2 Likes

Could you elaborate? I’m not seeing the target of & … expressions listed as coercion sites, whereas “final line of a block” is listed, which is – I suppose – why &{ … } makes a difference.

The coercion site is the function call argument, and coercion site propagation is not required, as one of the listed coercions is

TyCtor(T) to TyCtor(U), where TyCtor(T) is one of

  • ...
  • &T

and where U can be obtained by T by unsized coercion

And unsized coercion applies to Box<_>. Moreover, if this was a propagation issue, parenthetical subexpressions are listed. (There are also a number of other issues around coercion with blocks acting differently, though most of them seem to be about Deref coercion, e.g. 23014, 26978, 57749.)

You can’t coerce &Box<B> into &Box<dyn A>, in case that’s what you’re suggesting. (Let me read the remainder of your answer in more detail though…)

Yes, that's the bug by my reading of the RFC and reference :slight_smile:

Ah, so you’re saying, the reference suggest that coercing &Box<B> into &Box<dyn A> should be possible (while it’s of course fundamentally impossible in practice)?

Yes, the reference (and RFC 401) say it should, as

  • Box<T> unsize-coerces to Box<dyn Trait> (I could quote the rules here too)
  • &_ is a "TyCtor"
  • "TyCtor"s coerce if the _ unsize-coerce (coerce_inner in the RFC)

Why is it impossible [1]?


  1. and what does "fundamentally ... in practice" mean :sweat_smile: ↩︎

I don't think so. Box<B> to Box<dyn A> is not an unsized coercion -- Box<dyn A> is Sized.

Because Box<B> coerces into Box<dyn A> by modifying the box, turning it from a thing pointer to a fat pointer. Now, you can’t modify the box behind a second layer of indirection anymore which is why

&B -> &dyn A>
Box<B> -> Box<dyn A>

work, but any of

&&B -> &&dyn A>
Box<&B> -> Box<&dyn A> *
&Box<B> -> &Box<dyn A>
Box<Box<B>> -> Box<Box<dyn A>> *

fundamentally can’t work in practice, i.e. it’s impossible to write the assembly for any sound function implementing this coercion.

* Well… in these cases with Box<…> in the outer layer, it’s at least impossible without re-allocation.

1 Like

OK, I guess I'll quote that part too...

Foo<..., T, ...> to Foo<..., U, ...>, when

  • Foo is a struct
  • T implements Unsize<U>
  • The last field of Foo has a type involving T
  • If that field has type Bar<T>, then Bar<T> implements Unsized<Bar<U>>
  • T is not part of the type of any other fields

Additionally, a type Foo<T> can implement CoerceUnsized<Foo<U>> when T implements Unsize<U>or CoerceUnsized<Foo<U>>. This allows it to provied an unsized coercion to Foo<U>.

I think, the reference is weird about it’s wording what constitutes an “unsized coercion”. There’s the unsizing and the coercion, and the section about it mixes up the two a bit, in particular in the last section

Additionally, a type Foo<T> can implement CoerceUnsized<Foo<U>> when T implements Unsize<U> or CoerceUnsized<Foo<U>>. This allows it to provide a unsized coercion to Foo<U>.


In practice, the two concepts Foo: Unsize<Bar> and Foo: CoerceUnsized<Bar> are distinct, and only the latter is an actual coercion, while the former is an important consideration for determining whether or not certain CoerceUnsized implementations are met.

Yes, that makes sense, thanks.

Digressing from the language rules discussion, you probably don't want to do this. Instead, pass a reference to dyn A directly:

fn f(_: &dyn A) {}

fn main() {
    f(&*Box::new(B {}));
}
7 Likes

Also, the section

  • TyCtor(T) to TyCtor(U), where TyCtor(T) is one of

    • &T
    • &mut T
    • *const T
    • *mut T
    • Box<T>

    and where U can be obtained from T by unsized coercion.

Is kinda weird, because it seems to merely try to list the CoerceUnsize in the standard library while being awefully incomplete. I.e. this section appears to be a natural language description of this list of impls

impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T
where
    'b: 'a,
    T: Unsize<U> + ?Sized,
    U: ?Sized,

impl<'a, T, U> CoerceUnsized<&'a mut U> for &'a mut T
where
    T: Unsize<U> + ?Sized,
    U: ?Sized,

impl<T, U> CoerceUnsized<*const U> for *const T
where
    T: Unsize<U> + ?Sized,
    U: ?Sized,

impl<T, U> CoerceUnsized<*mut U> for *mut T
where
    T: Unsize<U> + ?Sized,
    U: ?Sized

impl<T, U, A> CoerceUnsized<Box<U, A>> for Box<T, A>
where
    T: Unsize<U> + ?Sized,
    A: Allocator,
    U: ?Sized,

while the standard library has a lot more CoerceUnsized impls.


Now I’m wondering why the impls for &T and &mut T are different w.r.t. the lifetimes. An oversight?

If I had to guess, that part of the documentation came almost straight out of the RFC with something bolted on later for CoerceUnsized.

Do the different impls have any practical impact? I guess they might actually -- I'm now reminded of how unsized coercion can apply variance to the dyn lifetime even in invariant position. You can always copy a &_ out of an invariant position to get variance back, but you can't copy a &mut out. I'm not sure if that's it or not, but perhaps.

The most important aspect are the additional impls. To name one example, there’s an impl for Pin that, translated into the style of that bulled point, would mean that the infinite list of cases

  • Pin<&T>>
  • Pin<&mut T>
  • Pin<*const T>
  • Pin<*mut T>
  • Pin<Box<T>>
  • Pin<Pin<&T>>>
  • Pin<Pin<&mut T>>
  • Pin<Pin<*const T>>
  • Pin<Pin<*mut T>>
  • Pin<Pin<Box<T>>>
  • Pin<Pin<Pin<&T>>>>

would need to be included

fn demonstration(x: Pin<Pin<Pin<&B>>>) -> Pin<Pin<Pin<&dyn A>>> { x }

playground

Sorry for being imprecise; I was referring to the different lifetime impls (covariant for &, invariant for &mut).

Ah, alright. Haven’t tested what practical impact those might have, but it reminded me of the dyn Trait + 'lifetime lifetimes, too, where e.g. Cell<Box<dyn Trait + 'lifetime>> can be coerced to a different lifetime.