rust-lang / rust-lang/rust

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

Open
#119,526 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-type-system C-bug T-compiler T-lang T-types
Dominant language
Rust
Stars
119k
Forks
16.2k
PR merge metrics
PR metrics pending

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the self-contained reproduction in src/main.rs and compile it with the reported rustc nightly version. Compare the behavior of foo, fee, fae, and bar, focusing on method selection inside the async block. Done means the equivalent async forms consistently select the applicable method or the behavior is clearly covered by a compiler test.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.