我在写下述代码时遇到闭包的生命周期问题:
#![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
是从哪里来的,要怎么才能重新标记它。 - 编译器还提示我两个闭包参数的生命周期不一致,但我不知道该如何对其进行统一的标记。
1
共 8 条评论, 1 页
评论区
写评论不知道我的理解是否正确
验证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
如何理解这里得明白两件事
明白了这两点再来回看上面例子,在第(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?
在方法名里标生命周期很不习惯
--
👇
lithbitren: 哈哈,rust就是生命周期太劝退了,除了生命周期其实都还好,只要业务上贯彻不存引用不写泛型不写trait的理念,基本不会有啥大的实现问题,性能仍然比gc语言更好。
一旦涉及引用,一不小心就生命周期报错,自己捣鼓半天都不一定能解决,只能发进社区问大佬,还好基本都能有大佬能答上来。
更坑的是有时候自己捣鼓半天,好不容易编译通过了,但其实并不一定符合预期,比如说作为模块build成功了,真正在main引入代码开始使用函数了才报错。
理论上甚至存在生命周期完全不报错,但执行仍然不一定符合预期的情况。
到现在也没有人总结出一套真正通用的struct/trait多引用生命周期标记方法,能够让初学者查表查公式就能标记成功的方法。
哈哈,rust就是生命周期太劝退了,除了生命周期其实都还好,只要业务上贯彻不存引用不写泛型不写trait的理念,基本不会有啥大的实现问题,性能仍然比gc语言更好。
一旦涉及引用,一不小心就生命周期报错,自己捣鼓半天都不一定能解决,只能发进社区问大佬,还好基本都能有大佬能答上来。
更坑的是有时候自己捣鼓半天,好不容易编译通过了,但其实并不一定符合预期,比如说作为模块build成功了,真正在main引入代码开始使用函数了才报错。
理论上甚至存在生命周期完全不报错,但执行仍然不一定符合预期的情况。
到现在也没有人总结出一套真正通用的struct/trait多引用生命周期标记方法,能够让初学者查表查公式就能标记成功的方法。
如果不想重复代码,可以整个宏包起来。
--
👇
github.com/shanliu/lsys: 这样应该没问题 为什么喜欢写那么绕的代码。
impl<T, U, R, F> FnOnceExt<T, U, R> for F where F: FnOnce(T) -> U 这样宽泛得范型实现 感觉是生怕编译出来的太小。。。
这样应该没问题 为什么喜欢写那么绕的代码。
不过 https://users.rust-lang.org/ 上的大佬们可能会有兴趣具体解释这其中的生命周期。(那里每天都有各种角度的生命周期问题)
只是一些琐碎的回复,不保证正确:
这很常见。有时候是正确的,有时候是错误的。这表明需要考虑生命周期。
Rust 的泛型除了类型泛型,还要考虑生命周期泛型,即 对于 T,你得考虑 T: 'lifetime。
注意:
impl Trait
有自己的 lifetime parameter/elision 规则,见 RFC 1951: expand-impl-trait,所以这个错误表明你的代码/实现含有编译器无法推理的生命周期关系,意味着你要显式指定生命周期关系(有可能实际无法指定)。就像 if_then 那样,写 unless_then 不是挺好的吗?