关于生rust:HRTB 的奇怪行为

Strange behavior of HRTBs

我有这个代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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();
}

编译失败:

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

在代码 2 中,我将 Fn(A) -> i32 更改为 Fn(&A) -> i32,代码有效。

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

由于 AFn 特征的参数,它是一个具有 Higher-Rank lifetime 的类型。它不应该受到 struct S<A> .

的生命周期的影响

但是为什么代码 1 不能编译呢?
如何解决借用或非借用类型 A 的问题?


没有简单的方法让 helper 在当前的 Rust 中工作,即使你删除了所有的 for<'a> A: Debug + 'a, 边界(这只会进一步限制 A 可以是什么类型,而你想允许更多)。

这就像我可以举出你的例子一样简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
}

它不起作用的原因是A"来自外部",而Rust无法推断出你想要for<'a> S<&'a A>,它甚至不能谈论这种类型.
请注意,如果 let arg = 333; 位于 let x 之上,则此示例会编译(因为它特别推断出对 arg 的引用,而不是 for<'a>)。

你今天能得到的最接近的是在一个带有生命周期参数的特征上的关联类型,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 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();
}

然而,事实证明,这种编码命中了 https://github.com/rust-lang/rust/issues/52812,所以它目前实际上不可用(而且我不知道一种解决方法)。


因为@eddyb 的workarkound 不起作用,所以我写了一个肮脏而危险的解决方法。

是的,它包含未定义的行为。但至少它在这个时候有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
use std::fmt::Debug;

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

impl<A> S<A>
where
    A: Debug + Sized,
{
    fn call< T >(&self, a: T)
    where
        T: Debug + Sized,
    {
        // assert_eq!(std::any::TypeId::of::<A>(), std::any::TypeId::of::< T >()); Not work because TypeId requires 'static lifetime
        // If TypeId::of supports non-static lifetime, we also need a compile-time type assert for better error message
        println!(
           "Return {:?}",
            unsafe {
                let b = std::mem::transmute::<&Box<Fn(A) -> i32>, &Box<Fn(T) -> i32>>(&self.f);
                let ret = b(a);
                std::mem::forget(b);
                ret
            }
        );
    }
}

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);
    x.call(&arg);
    x.call(&arg);
}

fn main() {
    helper();
}

也适用于 Fn Trait,这是我的目标。