Why can't I use lifetime bounds in HRTBs?

Consider the following code:

use std::borrow::Cow;

pub trait SomeTrait<'a, 'b: 'a> {
    fn foo<F>(&self, mut closure: F)
    where
        F: FnMut(&'a Cow<'b, str>),
    {
        closure(&Cow::Borrowed("static"))
    }
}

impl<'a, 'b: 'a> SomeTrait<'a, 'b> for () {}

// invalid syntax:
//fn bar<T: for<'a, 'b: 'a> SomeTrait<'a, 'b>>(_arg: T) {}

// makes `main` fail to compile:
fn bar<T: for<'a, 'b> SomeTrait<'a, 'b>>(_arg: T) {}

fn main() {
    bar(());
}

(Playground) (Playground) edit: renamed the function to "bar" to avoid confusion.

The real use-case is described here.

I wonder: is it impossible to have a HRTBs with two lifetimes where one lifetime outlives the other? Is there an open issue in the issue tracker? I wasn't able to find anything on that matter.


Note that it's possible to have an implicit bound:

use std::borrow::Cow;

fn works_fine<F: for<'a, 'b> FnMut(&'a Cow<'b, str>)>(mut closure: F) {
    let s = "Hello. I'm borrowed.".to_string();
    let cow = Cow::Borrowed(&*s);
    closure(&cow);
}

fn main() {
    works_fine(|cowref| println!("{cowref}"));
}

(Playground)

Output:

Hello. I'm borrowed.

1 Like

This is a longstanding limitation of the Rust trait system; it's just hard to implement. One of the major motivations of the ongoing trait solver refactor is to unblock this feature.

4 Likes

The latest status for that post: Rustc Trait System Refactor Initiative Update | Inside Rust Blog (published last week)
So maybe you could try -Ztrait-solver=next to see if it works.


I can't find some suitable materials or discussions about this syntax now, though I read some before.

But after some search, RFC: extended_hrtbs by tema3210 · Pull Request #3261 · rust-lang/rfcs · GitHub wants to support the syntax, but it's said to be unneeded in most cases.

2 Likes

When testing this with the real-world use case (the regex draft PR), I get lots of errors like this:

error: internal compiler error: compiler/rustc_middle/src/ty/instance.rs:413:18: failed to resolve instance for <[u8] as core::slice::cmp::SlicePartialEq<u8>>::equal

thread 'rustc' panicked at 'Box<dyn Any>', /rustc/f20afcc455bbcc5c0f7679450fb35fd0c9668880/compiler/rustc_errors/src/lib.rs:1650:9
stack backtrace:
   0:      0xf8ef48a40d1 - std::backtrace_rs::backtrace::libunwind::trace::hb020cba6b2cbc39e
                               at /rustc/f20afcc455bbcc5c0f7679450fb35fd0c9668880/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
…

Regarding the toy example in the first Playground, I get the same error:

error: lifetime bounds cannot be used in this context
  --> src/main.rs:15:23
   |
15 | fn bar<T: for<'a, 'b: 'a> SomeTrait<'a, 'b>>(_arg: T) {}

I feel like something weird is going on, and I suspect a compiler bug. Consider:

use std::borrow::Cow;

pub trait SomeTrait<T> {}
impl<'a, 'b: 'a> SomeTrait<&'a Cow<'b, str>> for () {}

fn works_fine<F: for<'a, 'b> FnMut(&'a Cow<'b, str>)>(mut closure: F) {
    let s = "Hello. I'm borrowed.".to_string();
    let cow = Cow::Borrowed(&*s);
    closure(&cow);
}
fn meeh<T: for<'a, 'b> SomeTrait<&'a Cow<'b, str>>>(_arg: T) {}

fn main() {
    works_fine(|cowref| println!("{cowref}")); // works fine
    meeh(()); // fails to compile
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: implementation of `SomeTrait` is not general enough
  --> src/main.rs:15:5
   |
15 |     meeh(()); // fails to compile
   |     ^^^^^^^^ implementation of `SomeTrait` is not general enough
   |
   = note: `()` must implement `SomeTrait<&'0 Cow<'1, str>>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `SomeTrait<&Cow<'2, str>>`, for some specific lifetime `'2`

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

Why does works_fine work fine while meeh(()) fails to compile?


Minimal example here:

use std::borrow::Cow;

pub trait SomeTrait<T> {}
impl<'a, 'b: 'a> SomeTrait<&'a Cow<'b, str>> for () {}

fn works_fine<T: for<'a, 'b> FnMut(&'a Cow<'b, str>)>(_arg: T) {}
fn meeh<T: for<'a, 'b> SomeTrait<&'a Cow<'b, str>>>(_arg: T) {}

fn main() {
    works_fine(|_| ()); // works fine
    meeh(()); // fails to compile
}

(Playground)

Changing

impl<'a, 'b: 'a> SomeTrait<&'a Cow<'b, str>> for () {}

to

impl<'a, 'b> SomeTrait<&'a Cow<'b, str>> for () {}

makes the example compile. Fixing implied bounds in higher-ranked trait bounds is one of the objectives mentioned in the trait solver refactor blog post.

1 Like

Just so I understand this better, aren't the two implementations the same?


Also consider the following, which compiles despite the 'b: 'a bound in foo:

use std::borrow::Cow;

pub fn foo<'a, 'b: 'a>() {}
pub fn bar<'a, 'b>(_arg: &'a Cow<'b, str>) { foo::<'a, 'b>() }

(Playground)

They should be the same, but are not for now I guess.

I noticed fixing this kind of problem is expected to be done in 2024 as per the roadmap where the linked issue is opened by you (surprising). I think the failed code deserves a new issue :rofl:

2 Likes

Oh. :sweat_smile:

You mean the code in this post above should be opened as an issue?

Yes, this one

I tried to modify the example a bit to make it clear that in either case 'b: 'a:

fn foo<'a, 'b: 'a>(_arg: &'a &'b i32) {}

trait Trait<T> {
    fn method(&self, arg: T);
}

impl<'a, 'b> Trait<&'a &'b i32> for ()
where
    'b: 'a, // `main` compiles if this is commented out
{
    fn method(&self, arg: &'a &'b i32) {
        foo(arg);
    }
}

fn works_fine1<'a, 'b>(arg: &'a &'b i32) { foo(arg) }
fn works_fine2<T: for<'a, 'b> FnMut(&'a &'b i32)>(_arg: T) {}
fn fails<T: for<'a, 'b> Trait<&'a &'b i32>>(_arg: T) {}

fn main() {
    works_fine1(&&0);
    works_fine2(|x| foo(x));
    fails(());
}

(Playground)


And here a further minimalized version:

trait Trait<T> {}
impl<'a, 'b: 'a> Trait<&'a &'b i32> for () {}

fn fails<T: for<'a, 'b> Trait<&'a &'b i32>>(_arg: T) {}

fn main() {
    fails(());
}

(Playground)


I created an issue with both code examples: HRTBs with two lifetimes: implementation is not general enough when impl uses lifetime bound #113967

2 Likes