HRTB的异常行为

时间:2018-09-23 09:14:36

标签: rust

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();
}

编译失败:

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

code 2中,我将Fn(A) -> i32更改为Fn(&A) -> i32,代码起作用了。

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

由于AFn特征的参数,因此它是具有Higher-Rank lifetime的类型。不应受结构S<A>的生存期影响。

但是为什么不能编译代码1?
对于借用或非借用类型A,我该如何解决?

2 个答案:

答案 0 :(得分:5)

即使删除所有helper边界(只有进一步限制什么类型{{1} },而您想允许更多)。

这很简单,我可以举一个例子:

for<'a> A: Debug + 'a,

之所以不起作用,是因为A是“从外面来的”,Rust无法推断出您想要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放在for<'a> S<&'a A>上方,则此示例可以编译(因为它会推断出对let arg = 333; 的引用,而不是let x)。

您今天可以获得的最接近的特征是具有寿命参数的特征的关联类型,例如:

arg

但是,事实证明,这种编码符合https://github.com/rust-lang/rust/issues/52812,因此目前实际上不可用(而且我不知道解决方法)。

答案 1 :(得分:-1)

{@ 3}因为@eddyb的工作原理不起作用,

它包含未定义的行为 。但是至少目前它可以正常工作。

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();
}

I wrote a dirty and danger workaround