Zz Tux
Zz Tux

Reputation: 658

Strange behavior of HRTBs

I have this code:

use std::fmt::Debug;

struct S<A>
where
    for<'a> A: Debug + 'a,
{
    f: Box<Fn(A) -> i32>,
}

impl<A> S<A>
where
    for<'a> A: Debug + 'a,
{
    fn call(&self, a: A) {
        println!("Return {:?}", (self.f)(a));
    }
}

fn create<A>(f: Box<Fn(A) -> i32>) -> S<A>
where
    for<'a> A: Debug + 'a,
{
    S::<A> { f }
}

fn helper() {
    let x = create::<&i32>(Box::new(|x: &i32| *x * 2));
    let arg = 333;
    x.call(&arg);
}

fn main() {
    let x = helper();
}

It's failed to compile:

error[E0310]: the parameter type `A` may not live long enough

In code 2, I changed Fn(A) -> i32 to Fn(&A) -> i32, the code works.

...
    f: Box<Fn(&A) -> i32>,
...

Since A is argument of Fn trait, it's a type that has Higher-Rank lifetime. It shouldn't be affected by the lifetime of struct S<A> .

But why can't code 1 be compiled?
How can I workaround it for borrow or non-borrow type A?

Upvotes: 4

Views: 223

Answers (1)

eddyb
eddyb

Reputation: 784

There is no easy way to make helper work in current Rust, even if you remove all the for<'a> A: Debug + 'a, bounds (which only further restricts what types A can be, whereas you want to allow more).

This is as simple as I can make your example:

struct S<A> {
    f: Box<Fn(A) -> i32>,
}

impl<A> S<A> {
    fn call(&self, a: A) {
        println!("Return {:?}", (self.f)(a));
    }
}

fn create<A>(f: Box<Fn(A) -> i32>) -> S<A> {
    S { f }
}

fn helper() {
    let x = create(Box::new(|x: &i32| *x * 2));
    let arg = 333;
    x.call(&arg);
}

fn main() {
    helper();
}

The reason it doesn't work is that A "comes from the outside", and Rust can't infer that you want for<'a> S<&'a A>, it can't even talk about such a type.
Note that if let arg = 333; is placed above let x, this example does compile (because it infers a reference to arg specifically, not a for<'a>).

The closest you can get today is with an associated type on a trait with a lifetime parameter, e.g.:

// Emulating `type Type<'a>` by moving `'a` to the trait.
trait Apply<'a> {
    type Type;
}
struct Plain<T>(std::marker::PhantomData<T>);
impl<'a, T> Apply<'a> for Plain<T> {
    type Type = T;
}
struct Ref<T: ?Sized>(std::marker::PhantomData<T>);
impl<'a, T: ?Sized + 'a> Apply<'a> for Ref<T> {
    type Type = &'a T;
}

struct S<A: for<'a> Apply<'a>> {
    f: Box<for<'a> Fn(<A as Apply<'a>>::Type) -> i32>,
}

impl<A: for<'a> Apply<'a>> S<A> {
    fn call<'a>(&self, a: <A as Apply<'a>>::Type) {
        println!("Return {:?}", (self.f)(a));
    }
}

fn create<A: for<'a> Apply<'a>>(
    f: Box<for<'a> Fn(<A as Apply<'a>>::Type) -> i32>,
) -> S<A> {
    S { f }
}

fn helper() {
    let x = create::<Ref<i32>>(Box::new(|x: &i32| *x * 2));
    let arg = 333;
    x.call(&arg);
}

fn main() {
    helper();
}

However, it turns out that this encoding hits https://github.com/rust-lang/rust/issues/52812, so it's not actually usable at the moment (and I'm not aware of an workaround).

Upvotes: 5

Related Questions