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

Rust 在调用时引用自身字段的结构上设置处理程序,rust,closures,lifetime,borrow-checker,Rust,Closures,Lifetime,Borrow Checker,我在一个模块中有一个struct,其中有一个带有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 }

我在一个模块中有一个struct,其中有一个带有
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)))
}
struct MyStruct{
名称:String,
f:Box()>,,
}
impl MyStruct{
pub fn set_f(&mut self,f:Box()>){
self.f=f
}
pub fn set_处理程序(&mut self,f:Box()>){
设h=| s:String |{
f(格式!(“{}{}”,self.name,s));
};
self.set_f(Box::new(h));
}
}
fn main(){
让我的结构=我的结构{
名称:String::from(“hello”),
新的(| |:字符串|()),
};
my_struct.set_处理程序(Box::new(| s:String | println!(“{}”,s)))
}

获取以下错误:

错误[E0495]:由于需求冲突,无法推断适当的生存期
-->src/main.rs:12:17
|
12 |设h=| s:String |{
|  _________________^
13 | | f(format!(“{}{}”,self.name,s));
14 | |         };
| |_________^
|
注意:首先,生命周期不能超过11:5在方法体上定义的匿名生命周期#1。。。
-->src/main.rs:11:5
|
11 |/pub fn set_处理程序(&mut self,f:Box()>){
12 | |设h=| s:String |{
13 | | f(format!(“{}{}”,self.name,s));
14 | |         };
15 | |
16 | | self.set|f(Box::new(h));
17 | |     }
| |_____^
=注意:…以便类型兼容:
应为&mut MyStruct
找到并修改MyStruct(&M)
=注意:但是,生存期必须对静态生存期有效。。。
=注意:…因此表达式是可赋值的:

预期的std::boxed::Box核心问题是,
h
在其实现中使用
self.name
f
。 默认情况下,Rust闭包通过引用捕获(借用),因此如果将回调存储在
MyStruct
中,则捕获的
f
将不够有效,因为在执行离开
set\u处理程序
块后,它将被销毁(删除)

另一个问题是,默认情况下,存储在
框中的值应该与
的静态值一样有效

编译器自动尝试为
&mut self
分配适当的生存期,它默认假定
self
应该在
set\u handler
函数执行时生存

实际上,您正在尝试创建自引用结构
MyStruct
在回调中引用自身。最简单的解决方案是只
克隆
名称并删除自引用

在这里,我们强制闭包拥有它内部使用的变量的所有权,并克隆
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())

}

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

Fn(T)->()
至少是一种特征,而不是一种类型。您可能会发现阅读更多内容很有帮助,请花些时间创建一个。对于终身错误(或者任何错误,真的),没有什么灵丹妙药;我们需要知道是什么代码导致了您遇到的错误。理想情况下,在新的货物项目上或中创建一些内容。更新描述并添加到游乐场的链接。感谢您提供详细的答案。克隆解决方案无法工作,因为处理程序闭包应该在调用它时使用当前值
name
,而不是在调用
set\u处理程序时使用。
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())
}