Rust: Indexing via `index` method and `[idx]` sugar works differently in `async` blocks/functions

Created on 3 Jun 2020  路  6Comments  路  Source: rust-lang/rust

My co-worker stumbled upon this curious case where failing typecheck can be made to succeed only by making "equivalent" lowerings to code. In particular the following snippet fails to compile because there's a !Send type being retained across the yield point:


Failing code example

use std::ops::Index;

/// A `Send + !Sync` for demonstration purposes.
struct Banana(*mut ());
unsafe impl Send for Banana {}

impl Banana {
    /// Make a static mutable reference to Banana for convenience purposes.
    ///
    /// Any potential unsoundness here is not super relevant to the issue at hand.
    fn new() -> &'static mut Banana {
        static mut BANANA: Banana = Banana(std::ptr::null_mut());
        unsafe {
            &mut BANANA
        }
    }
}

// Peach is still Send (because `impl Send for &mut T where T: Send`)
struct Peach<'a>(&'a mut Banana);

impl<'a> std::ops::Index<usize> for Peach<'a> {
    type Output = ();
    fn index(&self, idx: usize) -> &() {
        &()
    }
}

async fn baz(v: &()) {}

async fn bar() -> () {
    let peach = Peach(Banana::new());
    let r = &peach[0];
    baz(r).await;
    peach.index(0); // make sure peach is retained across yield point
}

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

pub fn main() {
    assert_send(bar())
}

This snippet will fail with the following error (playground):

error: future cannot be sent between threads safely
  --> src/main.rs:41:5

suggesting that a &peach is being retained across the yield point baz(r).await. What is curious, however, that lowering the indexing operation to a method call on Index will make code build (playground):


Succeeding code example

use std::ops::Index;

/// A `Send + !Sync` for demonstration purposes.
struct Banana(*mut ());
unsafe impl Send for Banana {}

impl Banana {
    /// Make a static mutable reference to Banana for convenience purposes.
    ///
    /// Any potential unsoundness here is not super relevant to the issue at hand.
    fn new() -> &'static mut Banana {
        static mut BANANA: Banana = Banana(std::ptr::null_mut());
        unsafe {
            &mut BANANA
        }
    }
}

// Peach is still Send (because `impl Send for &mut T where T: Send`)
struct Peach<'a>(&'a mut Banana);

impl<'a> std::ops::Index<usize> for Peach<'a> {
    type Output = ();
    fn index(&self, idx: usize) -> &() {
        &()
    }
}

async fn baz(v: &()) {}

async fn bar() -> () {
    let peach = Peach(Banana::new());
    let r = &*peach.index(0);
    baz(r).await;
    peach.index(0); // make sure peach is retained across yield point
}

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

pub fn main() {
    assert_send(bar())
}

I鈥檓 not sure quite yet whether its incorrect that we successfully build the latter example or incorrect in that we retain an immutable reference in the former example.


A-NLL A-async-await AsyncAwait-Triaged C-enhancement E-medium E-mentor T-compiler

Most helpful comment

@rustbot claim

All 6 comments

Self-assigning for mentoring (if someone is interested in working on this, please feel free to claim).

May be related to #30127

@tmandry I am interested in solving this issue or the related issue that you mentioned. I am still fresh in the desugaring process, so I would like to receive your mentoring instruction, if this issue has not been taken. Thank you!

@dingxiangfei2009 Sorry, I just saw this. You're welcome to pick this up!

I'm going to un-assign myself since I haven't had time to look into it. If you're still interested, say @rustbot claim.

We did talk about this issue in a recent meeting about this code. Notes are here and should be helpful to anyone interested in picking this up.

@rustbot claim

Was this page helpful?
0 / 5 - 0 ratings