Closed
Description
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’m 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.
Metadata
Metadata
Assignees
Labels
Area: Non-lexical lifetimes (NLL)Area: Async & AwaitAsync-await issues that have been triaged during a working group meeting.Category: An issue proposing an enhancement or a PR with one.Call for participation: Medium difficulty. Experience needed to fix: Intermediate.Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.Relevant to the compiler team, which will review and decide on the PR/issue.
Type
Projects
Status
Done