如何为Option<闭包>指定生命周期?

8

我试图在一个结构体上放置一个字段,该字段应该包含一个Option<closure>

然而,Rust告诉我必须指定生命周期(尽管我可能还没有完全理解这个)。我正在尽力这样做,但是Rust从来不满意我想出的东西。请看我的内联注释以获取我遇到的编译错误。

struct Floor{
    handler: Option<|| ->&str> //this gives: missing lifetime specifier 
    //handler: Option<||: 'a> // this gives: use of undeclared lifetime name `'a`
}

impl Floor {
    // I guess I need to specify life time here as well 
    // but I can't figure out for the life of me what's the correct syntax
    fn get(&mut self, handler: || -> &str){
        self.handler = Some(handler);
    }
}
2个回答

17

这变得有点棘手。

一般而言,每当您要在数据结构中存储一个借用引用(即,&类型)时,就需要为其命名生命周期。在此情况下,您通过使用 'a 取得了正确的跟踪,但是那个 'a 必须在当前作用域中引入。引入方式与引入类型变量相同。因此,要定义 Floor 结构体:

struct Floor<'a> {
    handler: Option<|| -> &'a str>
}

但这里还有另一个问题。闭包本身也是具有生命周期的引用,也必须被命名。因此,在这里有两个不同的生命周期!试试这个:

struct Floor<'cl, 'a> {
    handler: Option<||:'cl -> &'a str>
}

对于您的impl Floor,您还需要将这些生命周期引入作用域:

impl<'cl, 'a> Floor<'cl, 'a> {
    fn get(&mut self, handler: ||:'cl -> &'a str){
        self.handler = Some(handler);
    }
}

从技术角度来说,您可以将其缩减为一个生命周期并使用 ||:'a -> &'a str, 但这意味着返回的&str始终具有与闭包本身相同的生命周期,我认为这是一个不好的假设。


7
哇,这真是让我头晕。人们应该适应那种疯狂吗? :-S - Christoph
7
如果您认为这很疯狂,那么尝试实际使用您的“Floor”结构体可能会更糟糕。 - rodrigo
2
@Christoph,希望当非盒式闭包到来时,闭包的情况会变得更好。例如,您将能够在结构中存储堆箱闭包,而无需指定生命周期。 - Vladimir Matveev
@VladimirMatveev 现在(看起来是这样),它们已经被实现,但我仍然没有找到使用未装箱闭包的此案例的正确语法示例。 - shaman.sir
1
@shaman.sir,您可能想在这方面提出一个单独的问题,但简而言之,一种拥有的封装闭包类型可以被存储在一个结构体中,例如:Box<FnMut(int, int) -> String + 'static> - Vladimir Matveev
显示剩余5条评论

4

对于当前 Rust 版本 1.x 的答案:

有两种方法可以获得你想要的: 一个是未打包的闭包(unboxed closure),另一个是打包的闭包(boxed closure)。未打包的闭包非常快(大多数情况下,它们被内联),但它们会向结构体添加一个类型参数。打包的闭包在这里增加了一点自由度:它们的类型被一层间接性所抹去,但这会使它们变慢。

我的代码有一些示例函数,因此有点长,请谅解 ;)

未打包的闭包

完整代码:

struct Floor<F>
    where F: for<'a> FnMut() -> &'a str 
{
    handler: Option<F>,
}


impl<F> Floor<F> 
    where F: for<'a> FnMut() -> &'a str
{
    pub fn with_handler(handler: F) -> Self {
        Floor {
            handler: Some(handler),
        }
    }

    pub fn empty() -> Self {
        Floor {
            handler: None,
        }
    }

    pub fn set_handler(&mut self, handler: F) {
        self.handler = Some(handler);
    }

    pub fn do_it(&mut self) {
        if let Some(ref mut h) = self.handler {
            println!("Output: {}", h());
        }
    }
}

fn main() {
    let mut a = Floor::with_handler(|| "hi");
    a.do_it();

    let mut b = Floor::empty();
    b.set_handler(|| "cheesecake");
    b.do_it();
}

现在这段代码存在一些典型的问题:你不能简单地拥有多个Floor的Vec,并且每个使用Floor对象的函数都需要有自己的类型参数。另外,如果删除b.set_handler(|| "cheesecake");这一行,则代码不会编译,因为编译器缺少对b的类型信息。

在某些情况下,您不会遇到这些问题 - 在其他情况下,您需要另一种解决方案。

Boxed closures

完整代码:

type HandlerFun = Box<for<'a> FnMut() -> &'a str>;

struct Floor {
    handler: Option<HandlerFun>,
}

impl Floor {
    pub fn with_handler(handler: HandlerFun) -> Self {
        Floor {
            handler: Some(handler),
        }
    }

    pub fn empty() -> Self {
        Floor {
            handler: None,
        }
    }

    pub fn set_handler(&mut self, handler: HandlerFun) {
        self.handler = Some(handler);
    }

    pub fn do_it(&mut self) {
        if let Some(ref mut h) = self.handler {
            println!("Output: {}", h());
        }
    }
}

fn main() {
    let mut a = Floor::with_handler(Box::new(|| "hi"));
    a.do_it();

    let mut b = Floor::empty();
    b.set_handler(Box::new(|| "cheesecake"));
    b.do_it();
}

这个程序运行速度略慢,因为每个闭包都需要进行堆内存分配,而调用装箱的闭包时通常需要进行间接调用(CPU 不喜欢间接调用...)。

但是 Floor 结构体没有类型参数,因此您可以拥有一个它们的 Vec。 您还可以删除 b.set_handler(Box::new(|| "cheesecake"));,程序仍然可以正常工作。


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