Странное поведение HRTBs - PullRequest
       3

Странное поведение HRTBs

0 голосов
/ 23 сентября 2018

У меня есть этот код :

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

В код 2 , я изменил Fn(A) -> i32 до Fn(&A) -> i32, код работает.

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

Поскольку A является аргументом признака Fn, это тип, имеющий Higher-Rank lifetime.На него не должно влиять время жизни struct S<A>.

Но почему нельзя скомпилировать код 1?
Как обойти его для типа заимствования или не заимствования A?

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Поскольку обходной путь @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();
}

Также работает с Fn Trait, что является моей целью.

0 голосов
/ 23 сентября 2018

Нет простого способа заставить helper работать в текущем Rust, даже если вы удалите все for<'a> A: Debug + 'a, границы (которые только дополнительно ограничивают , какие типы A могут быть, тогда как вы хотите разрешить больше ).

Это так просто, как я могу привести ваш пример:

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>).

На сегодняшний день вы можете получить ближайший связанный типtrait с параметром времени жизни, например:

// 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,, поэтому в данный момент она фактически не используется (и я не знаю обходного пути)).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...