Skip to content

Compiler suggests HRTB for closure when only type annotation needed #158393

Description

@jamesmunns

Code

// https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5aa2ea5a3e0543f7cfe3585de30ca4d1
fn inner<F>(buf: &mut [u8], func: F) -> usize
where
    F: FnOnce(&mut [u8]) -> Result<usize, isize>
{
    match func(buf) {
        Ok(n) => n,
        Err(e) => e as usize,
    }
}

fn outer<F>(buf: &mut [u8], func: F) -> usize
where
    F: FnOnce(&mut [u8]) -> Result<usize, isize>
{
    // As written, this doesn't work and will give the following error:
    //
    // 
    //    --> src/main.rs:41:5
    //    |
    // 29 |     inner(buf, outer_closure)
    //    |     ^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
    //    |
    //   = note: closure with signature `fn(&'2 mut [u8]) -> Result<usize, isize>` must implement `FnOnce<(&'1 mut [u8],)>`, for any lifetime `'1`...
    //   = note: ...but it actually implements `FnOnce<(&'2 mut [u8],)>`, for some specific lifetime `'2`
    // 
    //
    // But if we replace `|buf|` with `|buf: &mut [u8]|`, it's totally fine!
    let outer_closure = |buf| {
        match func(buf) {
            Ok(n) => {
                println!("OK: {n}");
                Ok(n)
            }
            Err(e) => {
                println!("ERR: {e}");
                Err(e)
            }
        }
    };
    
    inner(buf, outer_closure)
}

fn main() {
    let mut x = [0u8; 16];
    let res = outer(&mut x, |buf| {
        if buf.len() % 2 == 0 {
            Ok(buf.len())
        } else {
            Err(buf.len() as isize)
        }
    });
    println!("{res:?}");
}

Current output

Compiling playground v0.0.1 (/playground)
error: implementation of `FnOnce` is not general enough
  --> src/main.rs:41:5
   |
41 |     inner(buf, outer_closure)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 mut [u8]) -> Result<usize, isize>` must implement `FnOnce<(&'1 mut [u8],)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 mut [u8],)>`, for some specific lifetime `'2`

error: could not compile `playground` (bin "playground") due to 1 previous error

Desired output

Some kind of output that either Just Works, or suggests:

- let outer_closure = |buf| {
+ let outer_closure = |buf: &mut [u8]| {

Rationale and extra context

This is a minimized version of some serialization code where I wanted to intercept a closure. I started looking into why HRTB was necessary here, but realized that the compilation error disappeared when giving the closure more explicit type annotations.

Other cases

Rust Version

Reproduces with the current stable 1.96.0 (linked on playground)

Anything else?

No response

Metadata

Metadata

Assignees

Labels

A-diagnosticsArea: Messages for errors, warnings, and lintsT-compilerRelevant to the compiler 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