Search code examples
rustcastingtrait-objects

How to cast Vec<Box<dyn SomeTrait + Send + Sync>> to Vec<Box<dyn SomeTrait + Sync>> in rust?


I'm failing to cast dyn SomeTrait + Send + Sync to dyn SomeTrait + Sync.

More specifically, I am trying to cast &Vec<Box<dyn SomeTrait + Send + Sync>> to &Vec<Box<dyn SomeTrait + Sync>>.

I tried simply reassining as suggested here as well as using as but neither approach worked.

How can I accomplish what I want?

The error message is

expected trait `ToSql + Sync`, found trait `ToSql + Send + Sync`

Edit: Also, it would be nice to know, why this doesn't just work, since the conversion should be possible because we know that each dyn SomeTrait + Send + Sync> satisfies SomeTrait + Sync


Solution

  • Let's start with the hard things. If you want an easy solution, jump to the end of the answer (the last section).


    So: the fact is, it indeed could work. Why doesn't it? Well, apparently there are some problems, but the main problem is probably that nobody worked on that. There is a thread in internals.rust-lang.org discussing that.

    First thing to do, we need to understand the difference between coercion and subtyping.

    Coercing type T to U means that when you have something of type T and you want to convert it to type U the compiler can do that for you automagically. You just need to declare that you want type U and the compiler will do its magic and give it for you. For example, a reference to an array (&[T; N]) it can be coerced to a reference to slice (&[T]). When you have a reference to an array and you pass it to something (e.g. a function) that expects a reference to a slice, the compiler automatically inserts code to convert the pointer to the array (what a reference to array is in assembly code) to a pointer + size (what a reference to slice is in assembly code).

    Generally, every conversion can be a coercion. It just needs to be built into the language, and the compiler has to know how to insert code to do it. However, Rust has the principle that expensive things should be explicit, and so, coercions (that are implicit) need to be cheap.

    The important thing for us to understand about coercions is that they're not transitive. That is, if type T can be coerced to U, that doesn't mean that G<T> can be coerced to G<U>. Or, in simpler words, the fact that &[T; N] can be coerced to &[T] doesn't mean that we can also coerce Vec<&[T; N]> to Vec<&[T]>. The reason is simple: since coercion needs to actually insert code to perform the conversion, we don't know how to generate the code for the wrapper type. The fact that we know to convert &[T; N] to &[T] (by simply attaching the size) doesn't mean we know to convert a vector of &[T; N]s to a vector of &[T]s, a thing that requires allocating and copying (since the size of &[T] is double the size of &[T; N]).

    A subtyping relationship is a completely different thing. If a type T is a subtype of U, that means that any T is also U. The classic example is base classes in OOP languages. A instance of Derived is also an instance of Base. This does mean that, like with coercions, a pointer to Derived can be converted to a pointer to Base, but unlike with coercions, this doesn't require inserting any conversion code.

    The only subtypings in Rust as of time of writing are lifetime relationships. For example, 'static is a subtype of any lifetime, and that means that &'static T is a subtype of any &'a T. This is why you can give &'static T where &'a T is expected.

    Because no code to convert is required, subtyping is transitive. You can convert Vec<&'static T> to Vec<&'a T>. The exception to that is variance, which I won't talk about now.


    Now to the important point: the conversion from dyn Trait + Send + Sync to dyn Trait + Sync (in other words, the removal of additional auto-trait bounds from dyn Trait) is a coercion and not subtyping. This is why you cannot convert &Vec<Box<dyn Trait + Send + Sync>> to &Vec<Box<dyn Trait + Sync>> - the conversion is not transitive.

    There is no reason why it couldn't be subtyping - no code is required for the conversion, other than that, as I already said, nobody worked on that.


    But can we still do that?

    Using unsafe code, yes; but also no.

    The conversion from &Vec<Box<dyn Trait + Send + Sync>> to &Vec<Box<dyn Trait + Sync>> is not allowed, period. Even assuming Box<dyn Trait + Send + Sync> and Box<dyn Trait + Sync> have the same layout, two Vecs containing them is not guaranteed to have the same layout too. So what you asked for cannot be done.

    But how about something similar: converting from Vec<Box<dyn Trait + Send + Sync>> to Vec<Box<dyn Trait + Sync>> (no reference). Or similarly, &[Box<dyn Trait + Send + Sync>] to &[Box<dyn Trait + Sync>]. Or even &Vec<Box<dyn Trait + Send + Sync>> to &[&(dyn Trait + Send + Sync)]?

    We can use Vec::from_raw_parts() (or std::slice::from_raw_parts()) to build the Vec (or the slice) without relying on its layout specifics. But we still need that a pointer to dyn Trait + AutoTrait will have the same layout as a pointer to dyn Trait. Does this hold?

    Well, currently, yes; and I believe it will also hold in the future. But it is not documented anywhere, so I would not rely on that.


    However, from your comments, it looks like all you want is to get a slice &[&(dyn SomeTrait + Sync)] and you do not even care if you allocate or not. Well, this can be done very easily:

    let v2: Vec<&(dyn SomeTrait + Sync)> = v.iter().map(|item| &**item as _).collect();
    let v2: &[&(dyn SomeTrait + Sync)] = v2.as_slice();