在调用时引用其自身字段之一的结构上设置处理程序
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)))
}
出现以下错误:
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.name
和 f
。
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
或克隆选项(视情况而定),因为这是最安全的方法。
我在一个模块中有一个结构体,它有一个 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)))
}
出现以下错误:
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.name
和 f
。
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
或克隆选项(视情况而定),因为这是最安全的方法。