Allow trait object creation with user-defined type?

Viewed 174

In Rust, references as well as Box<T>, Rc<T>, and Arc<T> allow the creation of a trait object (e.g., from Box<Type> to Box<dyn Trait>). But is there a way to allow the same conversion with a user-defined generic "smart pointer" type?

For example, MyBox<T> is a thin wrapper around Box<T> but the code below results in a compilation error:

use std::io::Write;

pub fn main() {
    let std_box: Box<Vec<u8>> = Box::new(Vec::new());
    let std_dyn: Box<dyn Write> = std_box;
    // ^ this conversion is allowed.

    let my_box: MyBox<Vec<u8>> = MyBox { t: Box::new(Vec::new()) };
    let my_dyn: MyBox<dyn Write> = my_box;
    // ^ this conversion is not allowed.
}

struct MyBox<T: ?Sized> {
    t: Box<T>,
}
error[E0308]: mismatched types
 --> traits/src/trait_objs.rs:7:36
  |
7 |     let my_dyn: MyBox<dyn Write> = my_box;
  |                 ----------------   ^^^^^^ expected trait object `dyn std::io::Write`, found struct `Vec`
  |                 |
  |                 expected due to this
  |
  = note: expected struct `MyBox<dyn std::io::Write>`
             found struct `MyBox<Vec<u8>>`
1 Answers

Unfortunately, it looks like the answer is "you don't" for Rust Stable. Rust is very conservative in which implicit coercions it allows. In particular, from the docs, we see the rule that's kicking in for Box.

Coercion is allowed between the following types:

...

  • 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.

where the relevant unsized coercion rule is

  • T to dyn U, when T implements U + Sized, and U is object safe.

There's not much room for special casing there, at least not in the current version of Rust.

However, if you're willing to dip into Nightly-only features, then you get the exciting CoerceUnsized trait, whose intended use case is... smart pointers to things which would coerce like a Box would.

#![feature(unsize, coerce_unsized)]

use std::ops::CoerceUnsized;
use std::marker::Unsize;

impl<T, U> CoerceUnsized<MyBox<U>> for MyBox<T> where T: Unsize<U>, U: ?Sized {}

This tells Rust we can coerce MyBox<T> to MyBox<U> if T is "basically the same" as U, for an appropriate definition of "basically the same". No functions are required on the implementation; the trait impl simply needs to exist.

Related