< 返回版块

hzqd 发表于 2023-02-09 19:21

Tags:生命周期

我在写下述代码时遇到闭包的生命周期问题:

#![feature(return_position_impl_trait_in_trait)]
use core::ops::Not;

trait FnOnceExt<T, U, R> {
    fn combine(self, g: impl FnOnce(U) -> R) -> impl FnOnce(T) -> R;
    fn compose(self, g: impl FnOnce(R) -> T) -> impl FnOnce(R) -> U;
}

impl<T, U, R, F> FnOnceExt<T, U, R> for F where F: FnOnce(T) -> U {
    /// Combining two functions.
    ///
    /// # Examples
    ///
    /// ```
    /// fn inc(arr: &[u8]) -> Vec<u8> { arr.iter().map(|byte| byte + 1).collect() }
    /// fn sum(v: Vec<u8>) -> u8 { v.iter().sum() }
    /// fn to_string(x: u8) -> String { x.to_string() }
    /// 
    /// let func = inc.combine(sum).combine(to_string);
    /// assert_eq!(s("45"), func(&[0, 1, 2, 3, 4, 5, 6, 7, 8]));
    /// ```
    fn combine(self, g: impl FnOnce(U) -> R) -> impl FnOnce(T) -> R {
        |x| g(self(x))
    }

    /// Combining two functions in reverse order.
    ///
    /// # Examples
    ///
    /// ```
    /// fn inc(arr: &[u8]) -> Vec<u8> { arr.iter().map(|byte| byte + 1).collect() }
    /// fn sum(v: Vec<u8>) -> u8 { v.iter().sum() }
    /// fn to_string(x: u8) -> String { x.to_string() }
    /// 
    /// let func = to_string.compose(sum).compose(inc);
    /// assert_eq!(s("45"), func(&[0, 1, 2, 3, 4, 5, 6, 7, 8]));
    /// ```
    fn compose(self, g: impl FnOnce(R) -> T) -> impl FnOnce(R) -> U {
        g.combine(self)  // |x| self(g(x))
    }
}

trait AnyExt1<R>: Sized {
    /// Returns `Some(f())` if it satisfies the given predicate function,
    /// or `None` if it doesn't.
    ///
    /// # Examples
    ///
    /// ```
    /// assert_eq!("Hello World".to_string().into_some(), "Hello".if_then(|s| s.starts_with("Hel"), |s| format!("{} World", s)));
    /// assert_eq!(None, "Hello".if_then(|s| s.starts_with("Wor"), |_| ()));
    /// ```
    fn if_then(self, r#if: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
        if r#if(&self) { Some(then(self)) } else { None }
    }

    /// Returns `Some(f())` if it doesn't satisfy the given predicate function,
    /// or `None` if it does.
    ///
    /// # Examples
    ///
    /// ```
    /// assert_eq!(None, "Hello".unless_then(|s| s.starts_with("Hel"), |_| ()));
    /// assert_eq!("Hello World".to_string().into_some(), "Hello".unless_then(|s| s.starts_with("Wor"), |s| format!("{} World", s)));
    /// ```
    fn unless_then(self, if_not: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
        self.if_then(Not::not.compose(if_not), then)
    }
}

impl<T, R> AnyExt1<R> for T {}

See: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2c70103194f10db77a69657d4552be40

  • 编译器提示我某些变量和引用要'static,但我不知道这个'static是从哪里来的,要怎么才能重新标记它。
  • 编译器还提示我两个闭包参数的生命周期不一致,但我不知道该如何对其进行统一的标记。

评论区

写评论
zhylmzr 2023-02-21 21:37

不知道我的理解是否正确

fn if_then(self, 
    r#if: impl FnOnce(&Self) -> bool,   // 把r#if的生命周期称之为'a, 因为&'a Self,那么 Self: 'a
    then: impl FnOnce(Self) -> R) 
-> Option<R> {
    if r#if(&self) { //------+ '1       // 我们把这个临时的借用称之为 '1,'1 能不能传入 'a?
    //                       |          // 问题在于 'a 的范围有多大?
    //-----------------------+
        Some(then(self))
    } else {
        None
    }
}

fn unless_then(
    self,
    if_not: impl FnOnce(&Self) -> bool,                     // 把 if_not 生命周期称之为 'b, 因为 &'b Self, 那么 Self: 'b
    then: impl FnOnce(Self) -> R,
) -> Option<R> { //----------------------------+ 'b     (A)
    // 为了说明拆成2步                           |
    let func1 = Not::not.compose(if_not);//    |        (B) // 已知compose返回值生命周期和入参生命周期一致 
                                         //    |            // 因为 `fn compose(self, g: impl FnOnce(**R**) -> T) -> impl FnOnce(**R**) -> U`
                                         //    |            // 把 func1 的生命周期称之为 '2, 那么 '2 = 'b
    self.if_then(func1, then)            //    |        (C) // 这一步把 '2 作为参数传入,那么 '2 = 'a = 'b
}//--------------------------------------------+            // 所以 'a 在整个 if_then 调用中都是有效的

验证1: 强制指定if_then生命周期

验证2: 注释掉 '1 传入 'a的代码行

报错中 'static 是哪来的? 这应该是其他错误导致编译器生命周期推测失败的案例,Not::not是 'static, 它使用了 if_not,而 if_not 又借用了 Self, 让编译器以为修复方法是加上 Self: 'static 其实if_not与Not::not的生命周期无关

这个问题怎么修复? 关键点在于报错消息中的 implementation of FnOnce is not general enough 如何理解

error: implementation of `FnOnce` is not general enough
  --> src\main.rs:80:9
   |
80 |         self.if_then(Not::not.compose(if_not), then)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: `impl FnOnce(&'2 Self) -> bool` must implement `FnOnce<(&'1 Self,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 Self,)>`, for some specific lifetime `'2`

这里得明白两件事

  1. 如果不指定生命周期或使用HRTB,那么这类的生命周期是 any lifetime,而指定生命周期的话, 生命周期的类别是 specific lifetime。
  2. 闭包参数中的生命周期无法提升

明白了这两点再来回看上面例子,在第(B)行,func1实际已经是个闭包,它生命周期 '2,无法提升成为 any lifetime, 所以在把它传入 if_then 时报错,不能把一个 specific lifetime 传给 any lifetime。

怎么修改才能通过编译?

把if_then的 any lifetime 修改成 specific lifetime,也就是上面的 验证1,但是这样修改之后在 if_then 中 self的借用无法和 r#if 一起使用了,所以为了if_then能够正常工作,修改它以使用&self

综上,这个例子关键错误有2个点,一个是闭包参数的生命周期无法提升,导致了 specific lifetime 传给了 any lifetime 报错。 另一个就是 if_then 中 r#if(&self), 这个 &self 的临时借用 '1 能不能传给 r#if 的 'a?

Snowmanzzz 2023-02-10 17:10

在方法名里标生命周期很不习惯

--
👇
lithbitren: 哈哈,rust就是生命周期太劝退了,除了生命周期其实都还好,只要业务上贯彻不存引用不写泛型不写trait的理念,基本不会有啥大的实现问题,性能仍然比gc语言更好。

一旦涉及引用,一不小心就生命周期报错,自己捣鼓半天都不一定能解决,只能发进社区问大佬,还好基本都能有大佬能答上来。

更坑的是有时候自己捣鼓半天,好不容易编译通过了,但其实并不一定符合预期,比如说作为模块build成功了,真正在main引入代码开始使用函数了才报错。

理论上甚至存在生命周期完全不报错,但执行仍然不一定符合预期的情况。

到现在也没有人总结出一套真正通用的struct/trait多引用生命周期标记方法,能够让初学者查表查公式就能标记成功的方法。

lithbitren 2023-02-10 00:48

哈哈,rust就是生命周期太劝退了,除了生命周期其实都还好,只要业务上贯彻不存引用不写泛型不写trait的理念,基本不会有啥大的实现问题,性能仍然比gc语言更好。

一旦涉及引用,一不小心就生命周期报错,自己捣鼓半天都不一定能解决,只能发进社区问大佬,还好基本都能有大佬能答上来。

更坑的是有时候自己捣鼓半天,好不容易编译通过了,但其实并不一定符合预期,比如说作为模块build成功了,真正在main引入代码开始使用函数了才报错。

理论上甚至存在生命周期完全不报错,但执行仍然不一定符合预期的情况。

到现在也没有人总结出一套真正通用的struct/trait多引用生命周期标记方法,能够让初学者查表查公式就能标记成功的方法。

github.com/shanliu/lsys 2023-02-10 00:06

如果不想重复代码,可以整个宏包起来。

--
👇
github.com/shanliu/lsys: 这样应该没问题 为什么喜欢写那么绕的代码。

fn if_then(self, r#if: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
        if r#if(&self) { Some(then(self)) } else { None }
    }

    fn unless_then(self, if_not: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
        if Not::not.compose(if_not)(&self) { Some(then(self)) } else { None }
    }
github.com/shanliu/lsys 2023-02-10 00:03

impl<T, U, R, F> FnOnceExt<T, U, R> for F where F: FnOnce(T) -> U 这样宽泛得范型实现 感觉是生怕编译出来的太小。。。

github.com/shanliu/lsys 2023-02-09 23:59

这样应该没问题 为什么喜欢写那么绕的代码。

fn if_then(self, r#if: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
        if r#if(&self) { Some(then(self)) } else { None }
    }

    fn unless_then(self, if_not: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
        if Not::not.compose(if_not)(&self) { Some(then(self)) } else { None }
    }
苦瓜小仔 2023-02-09 23:07

不过 https://users.rust-lang.org/ 上的大佬们可能会有兴趣具体解释这其中的生命周期。(那里每天都有各种角度的生命周期问题)

苦瓜小仔 2023-02-09 22:49

只是一些琐碎的回复,不保证正确:

编译器提示我某些变量和引用要'static

这很常见。有时候是正确的,有时候是错误的。这表明需要考虑生命周期。

Rust 的泛型除了类型泛型,还要考虑生命周期泛型,即 对于 T,你得考虑 T: 'lifetime

编译器还提示我两个闭包参数的生命周期不一致

注意:impl Trait 有自己的 lifetime parameter/elision 规则,见 RFC 1951: expand-impl-trait,所以这个错误表明你的代码/实现含有编译器无法推理的生命周期关系,意味着你要显式指定生命周期关系(有可能实际无法指定)。

但我不知道该如何对其进行统一的标记

就像 if_then 那样,写 unless_then 不是挺好的吗?

fn unless_then(self, if_not: impl FnOnce(&Self) -> bool, then: impl FnOnce(Self) -> R) -> Option<R> {
    if !if_not(&self) { Some(then(self)) } else { None }
    // 或者
    self.if_then(|s| !(if_not(s)), then)
}
1 共 8 条评论, 1 页