So the compiler must be doing something at the function call to 'unbox' the Box item. But does not like it when one has the same trait bound for both (for some reason).
Let me address the second sentence first. In the OP you had
let reader2 /* : Box<dyn BufRead> */ = get_reader(true);
let reader1 /* : BufReader<File> */ = BufReader::new(File::open("test1.txt").unwrap());
do_stuff_2(reader1, reader2);
And these are two different types -- and Rust doesn't do autoboxing. The problem wasn't the trait bound -- both implement the trait -- the problem was that you used a single type parameter, so the values you pass had to be the same type.
Now back to
So the compiler must be doing something at the function call to 'unbox' the Box item.
It's not unboxing anything here. When you call do_stuff_2
, you're just pasing the Box<dyn BufRead>
by value. Next, we can see how Box<dyn BufReader>
implements BufRead
:
impl<B: BufRead + ?Sized> BufRead for Box<B> {
#[inline]
fn fill_buf(&mut self) -> io::Result<&[u8]> {
(**self).fill_buf()
}
// ...
It's just calling dyn BufRead
's implementation of BufRead
. The method calls will take a reference of the **self
(i.e. the dyn BufRead
) and nothing is removed from the Box
.
Going deeper, there's also no need to recover the erased base type
At this point, it's important to understand a bit more about dyn Trait
, aka trait objects. Trait objects are
- dynamically sized (do not implement
Sized
)
- able to dynamically dispatch to methods via a vtable
- but not dynamically typed
That is, a trait object is a statically known type. The compiler supplies the implementation of Trait
for the type dyn Trait
.
What does that implementation look like? Basically, every time you interact with dyn Trait
, you're dealing with some sort of pointer like &dyn Trait
or Box<dyn Trait>
. Those pointers are wide pointers, containing one pointer to the data and another pointer to a vtable. The vtable holds type-erased versions of the methods that take a type-erased pointer.
Here's an article with more details. Heads-up, it was written before the dyn
keyword was used for trait objects, so it uses Foo
instead of dyn Foo
, for example.
Note that at no point does dyn Trait
implementation need to know what the erased base type was, so along with no unboxing, there is no (emulated) downcasting.
Box
has a special ability -- the compiler recognizes it's the unique owner of its contents, and lets you move the contents out via a dereference, even if the contents are not Copy
. (If they're not Copy
this consumes the Box
, naturally.) So the literal answer to your question is, you dereference it.
let unboxed = *bx;
...however that's not really what you're asking I imagine.
- You can't moved non-
Sized
things like a dyn BufRead
- You probably meant how do I get the unerased base type out
And in order to do that, you emulate dynamic typing by downcasting with the Any
trait.
You have a dyn BufRead
and not a dyn Any
, so this can't work without creating your own trait to wrap up the concept of BufRead + Any
or similar. That's it's own little topic, so I'll stop here.