Why does Rust dissalow upcasting from `dyn T + Send` to `dyn T`?

I noticed that I apparently cannot upcast from e.g &dyn T + Send to &dyn T. This seems to make it impossible to use dyn T receiver functions for objects of type dyn T + Send.

I understand why this doesn't work from a type theoretic standpoint; My question is: What's the underlying reason that Rust was designed this way? Intuitively, it seems to me that types implementing Send are a subset of all types, and so any object implementing Send sholuld also be usable "as a non-Send object". Is that reasoning wrong? Why does Rust dissalow this?

Here's an example where this might become a problem:
If a function was written as taking an &dyn T, it is (apparently) impossible to pass any object that was at some point marked with Send: &dyn T + Send is an invalid expression; taking &Box<dyn T + Send> isn't exactly clear code (as it apparenly is only needed to satisfy Rust's type system), and further requires allocation on the heap, which might be undesirable.

1 Like

This doesn't seem to be true? Counter-example.

(As a matter of terminology, if this is anything, it should be termed an upcast, not a downcast.)

You missed the parentheses (see Playground above).

Oh true! Okay, the issue is somewhere else then.

I actually only tested it as generic type parameters. E.g.: Box<&(dyn T + Send)> cannot be upcasted to Box<&dyn T>, is that correct?

Coercion to a dyn is a type of unsize coercion which can only take place behind a single layer of indirection -- Box<T> or &T, but not multiple layers like Box<&T>. That includes coercion from one dyn to another.

This doesn't work either, for example.

2 Likes

There is no deep reason, it's just something which wasn't implemented so far EDIT: actually it was, as several people noted. There is a recently accepted RFC for trait object upcasting, which would cover this case.

Note that you can always use unsafe casts if you really need to erase Send bound on current Rust:

fn upcast(val: &dyn Trait + Send) -> &dyn Trait {
    unsafe { 
        &*(val as *const dyn (Trait + Send))
            .cast::<dyn Trait>()
    }
}

Of course, it's unsafe and not ergonomic: this function needs to be written separately for every trait and set of autotraits (Send, Sync, Unpin), but at least you're not blocked.

Upcasting to remove auto-traits is already part of the (safe) language.

Allowing such upcasting in places where unsizing coercion can not apply more generally (ala subtyping) seems like a possible enhancement to me (since AFAIK an auto-trait won't influence a vtable), but isn't part of RFC 3324.