rust 闭包返回类型的生存期规范

7eumitmz  于 2023-08-05  发布在  其他
关注(0)|答案(2)|浏览(106)

我有下面的代码片段(生命周期尝试省略):

pub struct NamedArgument<T>(pub(in crate) &'static str, pub(in crate) T);

pub struct LoggedArgument<T>(pub(in crate) &'static str, pub(in crate) T);

impl<T> NamedArgument<T> {
    pub fn log_with<T2, F>(self, log_level: log::Level, transform: F) -> LoggedArgument<T>
    where
        T2: Display,
        F: FnOnce(&T) -> T2,
    {
        log::log!(log_level, "{} = {}", self.0, transform(&self.1));

        LoggedArgument(self.0, self.1)
    }
}

字符串
我将其命名为:

fn foo(argument: NamedArgument<impl AsRef<str>>) {
    argument.log_with(log::Level::Info, |s| s.as_ref());
}


但这会失败,并出现以下错误:

error: lifetime may not live long enough
  --> src/lib.rs:20:45
   |
20 |     argument.log_with(log::Level::Info, |s| s.as_ref());
   |                                          -- ^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                                          ||
   |                                          |return type of closure is &'2 str
   |                                          has type `&'1 impl AsRef<str>`


我确实理解这个问题(至少我是这么认为的),但发现自己无法正确地指定生命周期。本质上,我需要指定返回的T2的寿命超过传入闭包的参数。我已经尝试了几十种生存期规范的组合,包括为函数trait使用更高级别的trait边界。
如何指定使此系统正常工作所需的生存期?

omvjsjqw

omvjsjqw1#

不幸的是,这在当前的Rust中是不可能的。您有两种选择:
1.定义将&T转换为&T2的函数。这确实将其限制为返回引用的函数,因此您可以有两个函数,一个用于引用,另一个用于拥有的类型。这仍然不允许 all 类型,但允许绝对大多数类型。

impl<T> NamedArgument<T> {
    pub fn log_with<T2, F>(self, log_level: log::Level, transform: F) -> LoggedArgument<T>
    where
        T2: Display,
        F: FnOnce(&T) -> T2,
    {
        log::log!(log_level, "{} = {}", self.0, transform(&self.1));

        LoggedArgument(self.0, self.1)
    }
    
    pub fn log_with_reference<T2, F>(self, log_level: log::Level, transform: F) -> LoggedArgument<T>
    where
        T2: Display + ?Sized,
        F: FnOnce(&T) -> &T2,
    {
        log::log!(log_level, "{} = {}", self.0, transform(&self.1));

        LoggedArgument(self.0, self.1)
    }
}

fn foo(argument: NamedArgument<impl AsRef<str>>) {
    argument.log_with_reference(log::Level::Info, |s| s.as_ref());
}

字符串
1.为Transformer定义一个自定义trait。使用GATified trait,你可以覆盖所有可能的类型,并且你也可以通过为拥有的函数实现trait来为大多数情况提供闭包语法的便利,但是你必须使用不太方便的显式结构和trait实现其他所有内容:

pub trait Transformer<T> {
    type Output<'a>: Display
    where
        T: 'a;

    fn transform(self, arg: &T) -> Self::Output<'_>;
}

impl<T1, T2, F> Transformer<T1> for F
where
    F: FnOnce(&T1) -> T2,
    T2: Display,
{
    type Output<'a> = T2
    where
        T1: 'a;

    fn transform(self, arg: &T1) -> Self::Output<'_> {
        self(arg)
    }
}

impl<T> NamedArgument<T> {
    pub fn log_with<F>(self, log_level: log::Level, transform: F) -> LoggedArgument<T>
    where
        F: Transformer<T>,
    {
        log::log!(log_level, "{} = {}", self.0, transform.transform(&self.1));

        LoggedArgument(self.0, self.1)
    }
}

fn foo(argument: NamedArgument<impl AsRef<str>>) {
    argument.log_with(log::Level::Info, {
        struct MyTransformer<T>(std::marker::PhantomData<T>);

        impl<T: AsRef<str>> Transformer<T> for MyTransformer<T> {
            type Output<'a> = &'a str
            where
                T: 'a;

            fn transform(self, s: &T) -> Self::Output<'_> {
                s.as_ref()
            }
        }

        MyTransformer(std::marker::PhantomData)
    });
}


是的,很长。这就是劣势

deikduxw

deikduxw2#

如果你的闭包只返回引用,你可以显式地指定闭包返回类型为引用(及其生命周期),并使用更高排名的trait绑定:

pub fn log_with<T2, F>(self, log_level: log::Level, transform: F) -> LoggedArgument<T>
where
    T2: Display + ?Sized,
    F: for<'a> FnOnce(&'a T) -> &'a T2,
{
    log::log!(log_level, "{} = {}", self.0, transform(&self.1));

    LoggedArgument(self.0, self.1)
}

字符串
但是如果你不能保证这一点,你唯一的选择就是使用带有泛型关联类型(GAT)的自定义trait:

trait Transform {
    type Input;
    type Output<'a>;

    fn transform<'a>(self, input: &'a Self::Input) -> Self::Output<'a>;
}


这种方法的缺点是,您不能再使用闭包,必须手动创建一个新的结构体,该结构体为您可能需要进行的每个可能的转换实现Transform
对于闭包返回的类型 * 不是 * 引用的情况(即F: for<'a> FnOnce(&'a T) -> T2<'a>)在没有自定义trait方法的情况下将无法工作,因为Rust还不支持更高类型的概念。

相关问题