在调用时引用其自身字段之一的结构上设置处理程序

Setting a handler on a struct that references one of its own fields at call time

我在一个模块中有一个结构体,它有一个 Fn 类型的字段和一个 setter 方法,试图注册一个回调函数

struct MyStruct {
    name: String,
    f: Box<dyn Fn(String) -> ()>,
}

impl MyStruct {
    pub fn set_f(&mut self, f: Box<dyn Fn(String) -> ()>) {
        self.f = f
    }

    pub fn set_handler(&mut self, f: Box<dyn Fn(String) -> ()>) {
        let h = |s: String| {
            f(format!("{} {}", self.name, s));
        };

        self.set_f(Box::new(h));
    }
}

fn main() {
    let my_struct = MyStruct {
        name: String::from("hello"),
        f: Box::new(|_: String| ()),
    };
    my_struct.set_handler(Box::new(|s: String| println!("{}", s)))
}

playground

出现以下错误:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:12:17
   |
12 |           let h = |s: String| {
   |  _________________^
13 | |             f(format!("{} {}", self.name, s));
14 | |         };
   | |_________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 11:5...
  --> src/main.rs:11:5
   |
11 | /     pub fn set_handler(&mut self, f: Box<dyn Fn(String) -> ()>) {
12 | |         let h = |s: String| {
13 | |             f(format!("{} {}", self.name, s));
14 | |         };
15 | |
16 | |         self.set_f(Box::new(h));
17 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &&mut MyStruct
              found &&mut MyStruct
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::boxed::Box<(dyn std::ops::Fn(std::string::String) + 'static)>
              found std::boxed::Box<dyn std::ops::Fn(std::string::String)>

核心问题是,h 在其实现中使用了 self.namef。 Rust 闭包默认通过引用捕获(借用),所以如果你将回调存储在 MyStruct 中,捕获的 f 将不够活,因为它会在执行离开后被销毁(丢弃)[=18] =]块。

另一个问题是默认情况下存储在 Box<_> 中的值应该与 'static.

一样长。

编译器自动尝试为 &mut self 分配适当的生命周期,它默认假设 self 应该在 set_handler 函数执行时存在。

您实质上是在尝试创建自引用结构。 MyStruct 在回调中引用自身。最简单的解决方案是 clone 命名并删除自引用。

这里我们强制闭包获取其中使用的变量的所有权并克隆self.name,所以闭包不会借用self

    let name = self.name.clone();
    let h = move |s: String| {
        f(format!("{} {}", name, s));
    };

更复杂的解决方案是,告诉编译器 MyStruct 不能移动,因为自引用类型只有在它们不移动时才是安全的改变它们的引用(借用)地址并告诉编译器认为闭包中的数据应该与 MyStruct 一样长。这个比较复杂。

使用 Pin<_> 和一些精心编写的 unsafe 代码重构代码。

use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::NonNull;

// Alias supertrait
trait Callback: Fn(String) -> ()  {}
impl<T> Callback for T where T: Fn(String) -> ()  {}

struct MyStruct {
    name: String,
    handler: Box<dyn Callback>,
    _pin: PhantomPinned,
}

impl MyStruct {
    pub fn new() -> Pin<Box<MyStruct>> {
        Box::pin(MyStruct {
            name: String::from("hello"),
            handler: Box::new(|_| { Default::default() }),
            _pin: PhantomPinned,
        })
    }

    // Extracting callback is essentially safe, because if you save it, it can't live longer than pinned MyStruct. Lifetimes are elided.
    pub  fn get_handler_mut(self: Pin<&mut MyStruct>) -> &mut Box<dyn Callback> {
        unsafe {
            &mut self.get_unchecked_mut().handler
        }
    }

    pub fn set_handler(self: Pin<&mut MyStruct>, f: impl Callback + 'static) {
        // Create non null, raw pointer. Type is pinned and lifetimes are set by compiler for get_handler_mut(), everything is safe.
        let name = NonNull::from(&self.name);

        let wrapper = move |s: String| {
            // This is safe, because self is pinned, so name can't point to dangling pointer.
            let name = unsafe { name.as_ref() };

            f(format!("{} {}", name, s));
        };

        unsafe {
            // We know that assigning to `handler` will not move self, so it's safe.
            self.get_unchecked_mut().handler = Box::new(wrapper);
        }
    }
}


fn main() {
    let mut my_struct = MyStruct::new();

    my_struct.as_mut().set_handler(|s: String| {
        println!("{}", s)
    });


    let handler = my_struct.as_mut().get_handler_mut();
    (handler)("test".to_owned())

}

另一个安全但不太有效的解决方案是将 self.name 存储在引用计数智能指针中。 Rc 将自动管理 name 的生命周期。

use std::rc::Rc;

// Alias supertrait
trait Callback: Fn(String) -> ()  {}
impl<T> Callback for T where T: Fn(String) -> ()  {}

struct MyStruct {
    name: Rc<String>,
    handler: Box<dyn Callback>,
}

impl MyStruct {
    pub fn new() -> MyStruct {
        MyStruct {
            name: Rc::new("hello".to_owned()),
            handler: Box::new(|_| { Default::default() }),
        }
    }

    pub fn get_handler(&self) -> &Box<dyn Callback> {
        &self.handler
    }

    pub fn set_handler(&mut self, f: impl Callback + 'static) {
        let name = self.name.clone();
        let wrapper = move |s: String| {
            f(format!("{} {}", name, s));
        };
        self.handler = Box::new(wrapper);
    }
}


fn main() {
    let mut my_struct = MyStruct::new();

    my_struct.set_handler(|s: String| {
        println!("{}", s)
    });

    let handler = my_struct.get_handler();
    (handler)("test".to_owned())
}

结论。 Rust 中的自引用结构可能非常复杂,因为语言试图处理不安全问题并依赖于 RAII 和手动内存管理。没有 GC 来处理事情。在这个例子的生产中,我会使用 Rc 或克隆选项(视情况而定),因为这是最安全的方法。