How to remove this lifetime declaration?

This program cannot be compiled.

fn main() {
    let f = Foo(Box::new(|s, _| s.bytes()));
}

struct Foo<U>(Box<dyn for<'a> Fn(&'a str, &str) -> U + 'a>);
error[E0261]: use of undeclared lifetime name `'a`
  --> src\main.rs:16:56
   |
16 | struct Foo<U>(Box<dyn for<'a> Fn(&'a str, &str) -> U + 'a>);
   |            -                                           ^^ undeclared lifetime
   |            |
   |            help: consider introducing lifetime `'a` here: `'a,`

The compiler suggest me to add the lifetime declaration to the definition of Foo, but I think there is no necessity.
If I modify it to this, the program can be compiled.

use std::str::Bytes;
struct Foo(Box<dyn for<'a> Fn(&'a str, &str) -> Bytes<'a>>);

So I think Foo indeed does not need a lifetime declaration. How to remove the lifetime declaration in the generic type case?

The scope of the 'a introduced by the for keyword is limited to the Fn. The final 'a is unrelated to that, hence needs to be declared somewhere, which would be at the generic parameter list of Foo.

I'm sorry but my question is: why do I need to add a lifetime to the generic parameter list of Foo, not why I can't add 'a to U.

To be clear, you are not actually adding 'a to U here. The scoping is different. What you wrote is equivalent to this:

struct Foo<U>(Box<dyn 'a + for<'a> Fn(&'a str, &str) -> U>);

The reason that your code doesn't compile if you remove the + 'a is that the closure in main is trying to return the string, which would give it this siganture:

for<'a> fn(&'a str, &str) -> std::str::Bytes<'a>

However, your box defines U first, and then 'a, so U may not depend on 'a.

I don't there's any easy way to fix the definition of Foo to allow it. You can't "add" a lifetime to U after defining it, and you cannot define U inside the for<'a>.

But the problem is, I can write

let _:Foo<Bytes<'_>>=...

but I cannot write

struct Bar(Foo<Bytes<'_>>)

The useless lifetime parameter is annoying, is there no way out?

No, you can't elide lifetimes in structs.

If I define Foo as follows, it indeed could store |s| s.bytes(), but cannot execute it.

struct Foo<'a, U: 'a>(Box<dyn Fn(&'a str) -> U>);

fn main() {
    let f: Foo<Bytes<'_>> = Foo(Box::new(|s| s.bytes()));
    let k = "abc".to_string();
    f.0(&k);
}
error[E0597]: `k` does not live long enough
  --> src\main.rs:15:9
   |
15 |     f.0(&k);
   |         ^^ borrowed value does not live long enough
16 | }
   | -
   | |
   | `k` dropped here while still borrowed
   | borrow might be used here, when `f` is dropped and runs the destructor for type `Foo<'_, std::str::Bytes<'_>>`
   |
   = note: values in a scope are dropped in the opposite order they are defined

Indeed. Since 'a is a lifetime that is annotated on Foo, the lifetime must contain the region in which the Foo is alive. However, the destructor of k runs before Foo is destroyed, so you cannot borrow k for the lifetime 'a — values cannot be destroyed before the borrow ends.

In this specific instance you can fix it by swapping f and k so that k is destroyed after f, but I highly suspect that making the argument outlive the Foo would not be possible in your actual use-case.

fn main() {
    let k = "abc".to_string();
    let f: Foo<Bytes<'_>> = Foo(Box::new(|s| s.bytes()));
    f.0(&k);
}

Type parameters in Rust must resolve to a single type; they are not type constructors. So U alone cannot represent a borrowed return with an arbitrary lifetime, as each distinct lifetime is a distinct type.

Bytes<'_> works as it is a type constructor.

Your latest code sometimes works because you have restricted yourself to a specific lifetime.

You may be able to work around it with indirection through a trait (which acts as a type constructor), which I can demonstrate once at a non phone.

Sorry I don't understand, and I failed trying several ways using trait to solve it. I'm looking forward to your demonstration. XD

Here's an example where U remains a representative of the return type.

2 Likes

Thank you for your demo and detailed description. That's a good solution to this topic. :smiley:

1 Like

// In some cases I think this can be avoided by relying on the Fn traits
// instead, where the return type is an associated type.

Here's that approach, with a different trade-off (rustc fails to infer a higher-ranked closure; see the links in the code for more information and work-arounds).

And incidentally, #[unboxed_closures] removes the need for custom traits, but doesn't solve the inference issue. (The custom traits are needed because the Fn(&'a str, &str) -> Bytes<'a> sugar does not permit eliding the associated return type, which isn't so sweet in this case.)

Example.

(In this example and the prior, seemingly small changes confuse rustc quite a bit. E.g. trying to just put the bounds back on Foo itself.)

@quinedot I have another question based on the demo you gave: Rust Playground.

In my modified demo, I can't explain this phenomenon:

fn main() {
    // Another downside is that Rust fails to see through the indirection
    // let f = Foo(Box::new(|s, _| s.bytes()));
    
    // Success
    let f=my_func::<ReturnsBytes,_>(|s:&str, _| s.bytes());
    
    // Cannot compile
    //let f = Foo::<ReturnsBytes>::my_func(|s:&str, _| s.bytes());
}

If I let the code I commented out to be compiled, rustc will throw this error:

error[E0271]: type mismatch resolving `for<'a, 'r> <[closure@src/main.rs:63:42: 63:63] as FnOnce<(&'a str, &'r str)>>::Output == <ReturnsBytes as MaybeBorrowed<'a>>::Return`
  --> src/main.rs:63:13
   |
63 |     let f = Foo::<ReturnsBytes>::my_func(|s:&str, _| s.bytes());
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::str::Bytes`
   |
   = note: expected associated type `<ReturnsBytes as MaybeBorrowed<'_>>::Return`
                       found struct `std::str::Bytes<'_>`
   = help: consider constraining the associated type `<ReturnsBytes as MaybeBorrowed<'_>>::Return` to `std::str::Bytes<'_>` or calling a method that returns `<ReturnsBytes as MaybeBorrowed<'_>>::Return`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
note: required by a bound in `Foo::<U>::my_func`
  --> src/main.rs:51:40
   |
49 |     fn my_func<F>(func:F)
   |        ------- required by a bound in this
50 |     where
51 |         F:for<'a> Fn(&'a str, &str) -> <U as MaybeBorrowed<'a>>::Return
   |  

Could you explain the reason? Thanks.

I think it's a normalization bug, maybe this one. You can call the associated method from the free function and vice versa.

This attempted workaround ICE is definitely a bug.

Due to my lack of knowledge of internal implementation of Rust compiler, I can't understand what the root of this problem is. :thinking:

So... is there an elegant way to avoid this bug, except using standalone function?

Well, I did find one way -- run on nightly. Presumably there's a fix in the pipeline that will land on stable in a release or two.

Since it's broken in beta, probably in two (i.e. in 1.61, not in 1.60).

2 Likes

Oh, I'm glad to hear this bug will be fixed soon.... maybe. Using nightly toolchain as a transition is not a bad idea.