Why does an `impl Trait` return value implement Send while `Box<dyn Trait>` does not?
Asked Answered
S

2

7

The solution from How do I store a variable of type `impl Trait` in a struct? suggests creating a Future trait object. Doing that in my real code generates an error that the type is not Send, but the only difference between the working and non working version is the presence or absence of the cast to dyn Future.

Why does the compiler see these as different and how do I resolve the problem?

Here's a simplified version of the problem:

use std::future::Future;

fn uses_impl_trait() -> impl Future<Output = i32> {
    async { 42 }
}

fn uses_trait_object() -> Box<dyn Future<Output = i32>> {
    Box::new(async { 42 })
}

fn requires_send<T: Send>(_: T) {}

fn example() {
    requires_send(uses_impl_trait()); // Works
    requires_send(uses_trait_object()); // Fails
}
error[E0277]: `dyn std::future::Future<Output = i32>` cannot be sent between threads safely
  --> src/lib.rs:15:19
   |
11 | fn requires_send<T: Send>(_: T) {}
   |    -------------    ---- required by this bound in `requires_send`
...
15 |     requires_send(uses_trait_object());
   |                   ^^^^^^^^^^^^^^^^^^^ `dyn std::future::Future<Output = i32>` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `dyn std::future::Future<Output = i32>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<dyn std::future::Future<Output = i32>>`
   = note: required because it appears within the type `std::boxed::Box<dyn std::future::Future<Output = i32>>`

From Sending trait objects between threads in Rust, I already know that I can change the trait object to Box<dyn Future<Output = i32> + Send>, but why does this difference exist?

Swink answered 25/11, 2019 at 17:34 Comment(1)
Fair enough. I've drastically simplified and generalized the question to focus on the why.Unnumbered
U
2

For ergonomic reasons. RFC 1522, conservative impl trait, specifically discusses this design decision:

OIBITs leak through an abstract return type. This might be considered controversial, since it effectively opens a channel where the result of function-local type inference affects item-level API, but has been deemed worth it for the following reasons:

  • Ergonomics: Trait objects already have the issue of explicitly needing to declare Send/Sync-ability, and not extending this problem to abstract return types is desirable. In practice, most uses of this feature would have to add explicit bounds for OIBITS if they wanted to be maximally usable.

  • Low real change, since the situation already somewhat exists on structs with private fields:

    • In both cases, a change to the private implementation might change whether a OIBIT is implemented or not.
    • In both cases, the existence of OIBIT impls is not visible without documentation tools
    • In both cases, you can only assert the existence of OIBIT impls by adding explicit trait bounds either to the API or to the crate's test suite.

In fact, a large part of the point of OIBITs in the first place was to cut across abstraction barriers and provide information about a type without the type's author having to explicitly opt in.

This means, however, that it has to be considered a silent breaking change to change a function with an abstract return type in a way that removes OIBIT impls, which might be a problem. (As noted above, this is already the case for struct definitions.)

But since the number of used OIBITs is relatively small, deducing the return type in a function body and reasoning about whether such a breakage will occur has been deemed as a manageable amount of work.

See also:

Unnumbered answered 26/11, 2019 at 12:5 Comment(0)
M
0

Trait objects as a type, can be used in Struct:

Struct Foo {
    dbg: Box<dyn std::fmt::Debug>
}

If the Send/Syncness of Box<dyn Debug> depends on it value, then if struct Foo is Send and Sync would be not deterministic, as it depends on the value (not the type) as well.

Meliorate answered 26/7 at 8:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.