Rust 在调用时引用自身字段的结构上设置处理程序
我在一个模块中有一个struct,其中有一个带有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 }
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())
}