函数引用:预期绑定生命周期参数,但找到具体的生命周期[E0271]。

3

这个话题已经有很多讨论了,但我不确定之前讨论的问题是否适用于我的问题。

我有一个结构体,里面存储着一个name 和一个 callback函数。简化后的代码如下:

pub struct Command<'a> {
    name: &'a str,
    callback: &'a Fn(&[&str]) -> ()
}

impl <'a> Command<'a> {
    pub fn new(name: &'a str, callback: &'a Fn(&[&str]) -> ()) -> Command<'a> {
        Command {
            name: name,
            callback: callback
        }
    }
}

我想要做的是将回调函数与名称(和可能有的其他内容)关联存储。

但是当我尝试像这样使用以下代码时:

fn main() {
    let play_callback = |args| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", &play_callback);
}

我收到了以下错误信息:

src/main.rs:22:42: 22:56 error: type mismatch resolving `for<'r, 'r> <[closure@src/main.rs:16:22: 21:3] as std::ops::FnOnce<(&'r [&'r str],)>>::Output == ()`:
 expected bound lifetime parameter ,
    found concrete lifetime [E0271]
src/main.rs:22  let play_command = Command::new("play", &play_callback);
                                                        ^~~~~~~~~~~~~~

我试图像这样内联闭包

fn main() {
    let play_command = Command::new("play", &|args| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    });
}

但是我又遇到了另一个错误

src/main.rs:16:47: 21:7 error: borrowed value does not live long enough

我相信我理解为什么我得到它。
在切换到首先存储在我的Command结构中的函数引用之前,我尝试为Command使用通用类型参数,但当我想要像这样初始化命令对象的HashSet时:
let mut commands: HashSet<Command> = HashSet::new();

编译器要求我指定泛型参数,但我认为这是不可能的,因为这意味着我只能在所有Command对象中存储相同的闭包。
所以我的问题是:我该如何实现我想要的功能,最好的方法是什么(为什么),请指导。
2个回答

6
简单的解决方法(由ljedrz提供)是在这种情况下没有推断出args: &[&str]。然而,这可能不足以解决你的问题。
当你将某个特质的引用作为函数参数时,它被视为一个特质对象。在这种情况下,&Fn是一个特质对象,它引用堆栈上的闭包。
特质对象的简单类比是在其他语言中实现接口的对象。
然而,生命周期与特质对象有所不同。你可以把它们看作是“脱离”了通常的所有权流程。如果我们在你的示例中注释Fn特质对象的生命周期'c,我们将得到以下代码:
pub struct Command<'a> {
    name: &'a str,
    callback: &'a for<'c> Fn(&'c [&'c str]) -> ()
}

impl <'a> Command<'a> {
    pub fn new<'r>(name: &'r str, callback: &'r for<'c> Fn(&'c [&'c str]) -> ()) -> Command<'r> {
        Command {
            name: name,
            callback: callback
        }
    }
}

fn main() {
    let play_callback = |args: &[&str]| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", &play_callback);
}

在上述代码中,生命周期'c描述了回调函数将被调用的作用域。
然而,上述代码并不是很实用。它将命令与闭包创建的作用域耦合在一起(请记住,特性对象引用该作用域中的闭包)。因此,您无法退出创建Command的函数,因为那会销毁闭包!
可能的解决方案是将闭包存储在堆上,即Box<Fn(&[&str])>。盒子中特性对象的生命周期(堆内存)由盒子的创建和销毁控制,因此它是最广泛的('static)。
pub struct Command<'a> {
    name: &'a str,
    callback: Box<Fn(&[&str]) -> ()>
}

impl <'a> Command<'a> {
    pub fn new<'r>(name: &'r str, callback: Box<Fn(&[&str]) -> ()>) -> Command<'r> {
        Command {
            name: name,
            callback: callback
        }
    }
}

fn main() {
    let play_callback = |args: &[&str]| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", Box::new(play_callback));
}

在上面的例子中,闭包将在创建盒子时被移动到盒子中,并将与Command一起被销毁。

2

你尝试过指定args的类型吗?以下代码对我来说是可以编译的:

fn main() {
    let play_callback = |args: &[&str]| {
        println!("Playing something.");
        for arg in args {
            println!("{}", arg);
        }
    };
    let play_command = Command::new("play", &play_callback);
}

我不知道为什么它没有被推断出来。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接