rust-lang/rust

Unexpected behaviour when calling associated async function of a trait with default implementations

Samzyre opened this issue · 6 comments

I tried this code:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=e1f645e0ffe95f137fc42c12e783488b

#![feature(async_fn_in_trait)]

trait AsyncTrait {
    async fn default_impl() -> &'static str {
        "A"
    }

    async fn call_default_impl() -> &'static str {
        Self::default_impl().await
    }
}

trait SyncTrait {
    fn default_impl() -> &'static str {
        "A"
    }

    fn call_default_impl() -> &'static str {
        Self::default_impl()
    }
}

struct AsyncType;

impl AsyncTrait for AsyncType {
    async fn default_impl() -> &'static str {
        "B"
    }
}

struct SyncType;

impl SyncTrait for SyncType {
    fn default_impl() -> &'static str {
        "B"
    }
}

#[tokio::main]
async fn main() {
    let a = AsyncType::call_default_impl().await;
    let b = SyncType::call_default_impl();
    println!("{a}, {b}");
}

I expected to see this happen:
SyncType::call_default_impl calls default_impl from impl SyncTrait for SyncType.
My expectation was the same for AsyncType::call_default_impl of the same pattern.

Instead, this happened:
AsyncType::call_default_impl calls default_impl from the trait's default implementation,
instead of the one in impl AsyncTrait for AsyncType.

So the code prints A, B

Meta

rustc --version --verbose:

rustc 1.68.0-nightly (afaf3e07a 2023-01-14)
binary: rustc
commit-hash: afaf3e07aaa7ca9873bdb439caec53faffa4230c
commit-date: 2023-01-14
host: x86_64-pc-windows-msvc
release: 1.68.0-nightly
LLVM version: 15.0.6

I can take a look into this

there is more to this, not sure if it's covered by #107013: if the implementations of default_impl vary in their number of await points, then the program can crash with all sorts of errors (segfault, stackoverflow, etc) - example

Yea, it should be covered

I've made an MVCE for essentially the same issue, if that can be helpful: https://github.com/fasterthanlime/gh-107528

For anyone hitting this: seems like this a decent workaround for the time being is to go through a freestanding function that takes an &impl Trait:

bearcove/loona@2cae637#diff-b543f39ca4defdc86457f06aaef64e5fdad5fda92c91b4a98f486e7ab7b3b052R81-R87

@compiler-errors #108203 didn't fully fix this. the bug still persists when using polymorphic indirection via specialization:

#![feature(async_fn_in_trait)]
#![feature(min_specialization)]

struct MyStruct;

trait MyTrait<T> {
	async fn foo(_: T);
}

impl<T> MyTrait<T> for MyStruct {
	default async fn foo(_: T) {
		println!("default");
	}
}

impl MyTrait<i32> for MyStruct {
	async fn foo(_: i32) {
		println!("specialized");
	}
}

#[tokio::main]
async fn main() {
	MyStruct::foo(42).await;
	indirection(42).await;
}

async fn indirection<T>(x: T) {
	//explicit type coercion is currently necessary because of https://github.com/rust-lang/rust/issues/67918
	<MyStruct as MyTrait<T>>::foo(x).await;
}

(note that <MyStruct as MyTrait<i32>>::foo(42).await works just fine, so #67918 probably isn't at fault)

output is

specialized
default

should be

specialized
specialized