如何通过回调管理生命周期

How to manage lifetime through callbacks

我正在尝试创建一个函数,在给定回调的情况下,它可以对字符串执行一些操作,如果需要进行一些修改,returns 一个新分配的副本。我正在使用 std::borow::Cow 来做这样的事情,但是当我尝试使用回调来更改对字符串的操作执行方式时,问题就来了。

这是我正在尝试做的一个简化示例:

use std::borrow::Cow;
pub enum Error {
    Test,
}

fn do_lower_case<'a> (s: &'a str) -> Result<Cow<'a, str>, Error>
{   
    let s = s.to_lowercase();
    Ok(s.into())
}

fn say_hello<'a>(
    f: impl Fn(&'a str) -> Result<Cow<'a, str>, Error>,
) -> Result<Cow<'a, str>, Error> 
{   
    let s = String::from("Hello");

    // Problem: We can not call the callback from here.
    // Nevertheless we can call do_lower_case
    let s = f(&s)?; // Comment this and uncomment next line works
    //let s = do_lower_case(&s)?;

    let s = s.into_owned();
    Ok(s.into())
}

fn main() {
    let res = say_hello(do_lower_case); // Callback is provided here
    match res {
        Ok(s) => println!("Result: {}", s),
        Err(_) => println!("Could not do anything!"),
    }
}

Rust 可以做这样的事情吗? 这是一个 link 到铁锈游乐场: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8607d8ac4c34d02236e82e30bcf51e2e

编译器报错,因为s的生命周期总是比'a所能提供的生命周期短。

要解决这个问题,您可以使用 higher-ranked lifetimes。您可以指定一个仅适用于闭包的生命周期,而不是 say_hello 函数中的通用生命周期。这是通过在 implFn 之间添加 for<'a> 来完成的。在那之后,say_hello 也不能 return Cow<'a, str>,并且需要 return String.

fn say_hello(f: impl for<'a> Fn(&'a str) -> Result<Cow<'a, str>, Error>) -> Result<String, Error> {
    let s = "Hello";
    let s = f(&s)?;
    let s = s.into_owned();
    Ok(s)
}

或者,以下内容可能更具可读性:

fn say_hello<F>(f: F) -> Result<String, Error>
where
    F: for<'a> Fn(&'a str) -> Result<Cow<'a, str>, Error>,
{
    let s = "Hello";
    let s = f(&s)?;
    let s = s.into_owned();
    Ok(s)
}

也将 let s = String::from("Hello"); 更改为 let s = "Hello";


在这种情况下,您还可以依赖 lifetime elision 并可选择使用占位符生命周期 '_。那么就变成了:

fn say_hello<F>(f: F) -> Result<String, Error>
where
    F: Fn(&str) -> Result<Cow<str>, Error>,
{

fn say_hello<F>(f: F) -> Result<String, Error>
where
    F: Fn(&'_ str) -> Result<Cow<'_, str>, Error>,
{

您也可以像原来那样使用 impl Fn... 内联任何一个。