Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

traits

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?

like image 393
Delta_Fore Avatar asked Nov 25 '19 17:11

Delta_Fore


1 Answers

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:

  • What is an auto trait in Rust?
like image 88
Shepmaster Avatar answered Jan 04 '23 07:01

Shepmaster