Skip to content

Failure to consistently choose between inherent impl and trait method inside an async block #119526

Open
@BattyBoopers

Description

@BattyBoopers

I tried this code:

use core::ptr;
use std::fmt::Debug;
use std::future::Future;
use std::marker::PhantomData;

struct MaybeDebug<T>(PhantomData<T>);
impl<T> MaybeDebug<T> {
    fn new(_: *const T) -> Self {
        Self(PhantomData)
    }
}

impl<T: Debug> MaybeDebug<T> {
    // This method is preferred, but only exists if T has a Debug implementation
    pub fn maybe_debug(&self) -> Option<&dyn Debug> {
        Some(&self.0)
    }
}

trait NoDebug {
    // The trait method should take over if there is no debug implementation.
    fn maybe_debug(&self) -> Option<&dyn Debug> {
        None
    }
}
impl<T> NoDebug for MaybeDebug<T> {}

// There is no Debug impl for X, so the trait method should be chosen for X
pub struct X;

// This works
fn foo() -> X {
    let p = ptr::null();
    if false {
        return unsafe { ptr::read(p) };
    }
    let q = MaybeDebug::new(p);
    println!("{:?}", q.maybe_debug());
    X
}

// This works too
fn fee() -> impl Fn() -> X {
    || {
        let p = ptr::null();
        if false {
            return unsafe { ptr::read(p) };
        }
        let q = MaybeDebug::new(p);
        println!("{:?}", q.maybe_debug());
        X
    }
}

// Even this works
async fn fae() -> X {
    let p = ptr::null();
    if false {
        return unsafe { ptr::read(p) };
    }
    let q = MaybeDebug::new(p);
    println!("{:?}", q.maybe_debug());
    X
}

// This doesn't
fn bar() -> impl Future<Output = X> {
    async {
        let p = ptr::null();
        if false {
            return unsafe { ptr::read(p) };
        }
        let q = MaybeDebug::new(p);
        println!("{:?}", q.maybe_debug());
        X
    }
}

fn main() {}

In each function the type of variable p is constrained to be *const X.
This constrains q to be of type MaybeDebug<X>.
Since X doesn't have a Debug impl, the inherent impl MaybeDebug::<X>::maybe_debug doesn't exist and the trait method <MaybeDebug::<X> as NoDebug>::maybe_debug is chosen instead.
Especially fae and bar should behave absolutely identical.
Instead bar fails to compile with following error:

error[E0277]: `X` doesn't implement `Debug`
  --> src/main.rs:74:28
   |
74 |         println!("{:?}", q.maybe_debug());
   |                            ^^^^^^^^^^^ `X` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `X`
   = note: add `#[derive(Debug)]` to `X` or manually `impl Debug for X`
note: required by a bound in `MaybeDebug::<T>::maybe_debug`
  --> src/main.rs:13:9
   |
13 | impl<T: Debug> MaybeDebug<T> {
   |         ^^^^^ required by this bound in `MaybeDebug::<T>::maybe_debug`
14 |     // This method is preferred, but only exists if T has a Debug implementation
15 |     pub fn maybe_debug(&self) -> Option<&dyn Debug> {
   |            ----------- required by a bound in this associated function
help: consider annotating `X` with `#[derive(Debug)]`
   |
29 + #[derive(Debug)]
30 | pub struct X;
   |

For more information about this error, try `rustc --explain E0277`.
error: could not compile `fooobar` (bin "fooobar") due to 1 previous error

Meta

rustc --version --verbose:

rustc 1.77.0-nightly (e51e98dde 2023-12-31)
binary: rustc
commit-hash: e51e98dde6a60637b6a71b8105245b629ac3fe77
commit-date: 2023-12-31
host: x86_64-unknown-linux-gnu
release: 1.77.0-nightly
LLVM version: 17.0.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-type-systemArea: Type systemC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team, which will review and decide on the PR/issue.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions