rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.43k stars 12.73k forks source link

Fn[Mut] trait missing lifetime parameter #80421

Open ijackson opened 3 years ago

ijackson commented 3 years ago

As far as I can tell, there is no way to write a trait bound for this closure:

    let mut k : usize = 0;
    let mut closure = || &mut k;

The closure's call function takes &'something mut self, but there is no way to talk about the lifetime something. But it is necessary to talk about 'something because the return value borrows from it.

The following program compiles and works, using transmute to promise that the lifetimes are fine. Without the transmute, and removing the spurious 'static, there does not seem to be a way to write the bound on F.

use std::ops::FnMut;
use std::mem;

fn x<F>(f: &mut F)
    where F: FnMut() -> &'static mut usize
{
    *(f())= 42;
}

fn main() {
    let mut k : usize = 0;
    let mut closure = || unsafe { mem::transmute(&mut k) };
    x(&mut closure);

    println!("Hello, world! {}", k);
}

https://gist.github.com/rust-play/dd4471240c781ec26d07139045097b26

I hope I am right in describing this as a bug, although it seems like it's an API/language bug :-/. I tried various approaches, including for<>, and various lifetime annotation approaches, without anything resembling success. ISTM that what's really needed is for the FnMut and FnOnce traits to have a lifetime parameter for the self reference that the call function gets. I looked in the Reference and the Nomicon and various other places but the documentation in this area was rather thin and informal and didn't seem to come close to addressing this matter.

To demonstrate that what I am doing here is not unreasonable, here is a program which achieves the same thing with a "closure" which is actually a struct so has to be called with a method.

trait NullaryCallableMut<'f> {
    type Output;
    fn call(&'f mut self) -> Self::Output;
}

fn x<'f, F>(f: &'f mut F)
    where F: NullaryCallableMut<'f, Output=&'f mut usize>
{
    *(f.call())= 42;
}

struct Closure<'r>(&'r mut usize);

impl<'f> NullaryCallableMut<'f> for Closure<'f> {
    type Output = &'f mut usize;
    fn call(&'f mut self) -> &'f mut usize { self.0 }
}

fn main() {
    let mut k : usize = 0;

    let mut closure = Closure(&mut k);
    x(&mut closure);

    println!("Hello, world! {}", k);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f5add6be7376191011c0367159b8a617

All of these experiments were in the Rust playground, stable channel. 1.48 currently.

ohsayan commented 3 years ago

@rustbot modify labels to +A-lifetimes +A-traits -T-lang +T-compiler

jonas-schievink commented 3 years ago

As far as I can tell, there is no way to write a trait bound for this closure:

    let mut k : usize = 0;
    let mut closure = || &mut k;

You can't write a trait bound for this because it already fails to compile:

error: captured variable cannot escape `FnMut` closure body
 --> src/main.rs:4:26
  |
3 | let mut k : usize = 0;
  |     ----- variable defined here
4 |     let mut closure = || &mut k;
  |                        - ^^^^^-
  |                        | |    |
  |                        | |    variable captured here
  |                        | returns a reference to a captured variable which escapes the closure body
  |                        inferred to be a `FnMut` closure
  |
  = note: `FnMut` closures only have access to their captured variables while they are executing...
  = note: ...therefore, they cannot allow references to captured variables to escape
ijackson commented 3 years ago

Hi, thanks for looking at this.

You can't write a trait bound for this because it already fails to compile:

  = note: `FnMut` closures only have access to their captured variables while they are executing...
  = note: ...therefore, they cannot allow references to captured variables to escape

Oh! How sad. I don't know how in all my attempts I never managed to see that message, which explains the problem very nicely.

I don't think there is anything incoherent about this program - see my example simulating a closure with a struct. It would also be nice if one could impl FnMut for that struct.

But yes I was wrong to call this a bug. Thanks for adjusting the labels and sorry for the noise. (I had a look through other issues relating to closures and didn't find anything relevant.)

RodBurman commented 3 days ago

It seems like this was never a real problem and should be closed?