Pre-RFC async constructs (auto generated futures) should implement Sync unconditionally

async constructs (auto generated futures) should implement Sync unconditionally, as there is no way to get access to the inner data without gaining mutable access

currently this fails even though it shouldn't

fn should_compile() {
    use std::cell::Cell;

    fn assert_send_sync<T: Send + Sync>(_: T) {}

    assert_send_sync(async {
        let cell = Cell::new(0);
        macro_rules! increment {
            () => { cell.set(cell.get()+1) };
        }
        
        increment!();
        increment!();

        for _ in 0..10 {
            increment!();
            async {  }.await;
        }

        assert_eq!(cell.get(), 12);
    })
}
error: future cannot be shared between threads safely
  --> src/main.rs:7:5
   |
7  | /     assert_send_sync(async {
8  | |         let cell = Cell::new(0);
9  | |         macro_rules! increment {
10 | |             () => { cell.set(cell.get()+1) };
...  |
20 | |         assert_eq!(cell.get(), 14);
21 | |     })
   | |______^ future created by async block is not `Sync`
   |
   = help: within `{async block@src/main.rs:7:22: 7:27}`, the trait `Sync` is not implemented for `Cell<i32>`, which is required by `{async block@src/main.rs:7:22: 7:27}: Sync`
   = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead
note: future is not `Sync` as this value is used across an await
  --> src/main.rs:17:24
   |
8  |         let cell = Cell::new(0);
   |             ---- has type `Cell<i32>` which is not `Sync`
...
17 |             async {  }.await;
   |                        ^^^^^ await occurs here, with `cell` maybe used later
note: required by a bound in `assert_send_sync`
  --> src/main.rs:5:35
   |
5  |     fn assert_send_sync<T: Send + Sync>(_: T) {}
   |                                   ^^^^ required by this bound in `assert_send_sync

this for example, means these futures can go in a Vec<Box<dyn Future+Sync>> that’s part of a larger data structure that needs to be Sync without requiring an additional wrapper like std::sync::Exclusive

1 Like

That raises a question: what should happen if an async fn incorporates raw pointers, or other non-threadsafe types?

1 Like

I don't quite follow, I mean for the Send part of the future, that is still very much tracked, its just that all futures, weather they have non sync types or not, are still sync

1 Like

Sounds like that would make types Send that shouldn't have that marker trait in the first place, no different from sync Rust.

The future itself will not become Send from !Send. Yes, some type that contains the reference to the future and previously !Send might become Send. But that doesn't break anything, it's just a direct consequence.

There's even a crate doing exactly this : sync_wrapper.

1 Like

This would prevent any future addition of methods or impl of traits with methods taking &self to such futures, as then Sync would actually become wrong.

IMO if you need your Future to be Sync then use Exclusive instead. It implements Future, so you can use it everywhere a Future is expected.


That said, the future in your example is not only !Sync but also !Send, so neither Exclusive nor your proposal will make it compile. What you really need to make it compile is a weaker notion of Send for data that never escapes the future, but AFAIK this never got any formal proposal as it's very difficult to find a way to guarantee the requirement the the data "never escapes the future".

2 Likes

This would prevent any future addition of methods or impl of traits with methods taking &self to such futures, as then Sync would actually become wrong.

there is no way for you to use that &self, to get any data out of it, so no it would still be fine, and I really don't see extending opaque types as a future possibility, as the rust book says

Opaque types are a kind of type alias. They are called opaque because, unlike an ordinary type alias, most Rust code (e.g., the callers of as_u32s ) doesn't know what type AsU32sReturn represents. It only knows what traits that type implements (e.g., IntoIterator<Item = u32> ). The actual type that is inferred for AsU32sReturn is called the "hidden type".

so if the type really is hidden, I don't think there are plans to unhide it to make it possible extend

IMO if you need your Future to be Sync then use Exclusive instead. It implements Future, so you can use it everywhere a Future is expected.

Exclusive, is unstable, and is no where near being stable

That said, the future in your example is not only !Sync but also !Send , so neither Exclusive nor your proposal will make it compile. What you really need to make it compile is a weaker notion of Send for data that never escapes the future, but AFAIK this never got any formal proposal as it's very difficult to find a way to guarantee the requirement the the data "never escapes the future".

yeah fair enough, I've since updated the post

My point is that there is no way now. Implementing your suggestion would preclude adding any new way in the future, for example is any need for an optional &self method on Future is found.

Moreover the type of an async block is not opaque, it's just unnameable (just like the types of closures).

You are quoting the chalk book, which is not used in the rust compiler and likely never will.

If this proposal was implemented it likely wouldn't be immediately stable either.

1 Like