首先这个为什么在 Rust 中这么难实现?
我们得承认一个残酷的现实:Rust 不是 C++。在 C++ 中,可以随心所欲地使用引用,编译器不会过多干涉。但在 Rust 中,编译器会像严厉的监管者一样盯着我们的每一个动作,生怕我们搞出什么内存安全问题。
struct Args {
callback: Option<Box<dyn Fn()>>, // 这样写是不行的!
}
impl Args {
fn add_help(&mut self) {
self.callback = Some(Box::new(|| {
self.print_help(); // 编译器: 不行!这里可能会有循环引用!
}));
}
}
编译器会义正言辞地告诉你:「不行!这样可能会造成循环引用!」
柳暗花明又一村
但别灰心!Rust 虽然严格,但也提供了解决方案。这里有几种方法:
1.使用 Rc 和 RefCell 组合拳
use std::rc::Rc;
use std::cell::RefCell;
struct Args {
callback: Option<Box<dyn Fn()>>,
}
impl Args {
fn new() -> Rc<RefCell<Args>> {
let args = Rc::new(RefCell::new(Args {
callback: None,
}));
let args_clone = args.clone();
args.borrow_mut().callback = Some(Box::new(move || {
args_clone.borrow().print_help();
}));
args
}
}
2.使用 raw pointer(不推荐,但是可行)
struct Args {
callback: Option<Box<dyn Fn()>>,
}
impl Args {
fn new() -> Args {
let mut args = Args {
callback: None,
};
let args_ptr = &args as *const Args;
args.callback = Some(Box::new(move || {
unsafe {
(*args_ptr).print_help();
}
}));
args
}
}
3.重新设计 API(推荐)
struct Args {
callback: Option<Box<dyn Fn(&Args)>>, // 把 self 作为参数传入
}
impl Args {
fn add_help(&mut self) {
self.callback = Some(Box::new(|args| {
args.print_help();
}));
}
}
所以最佳实践是什么?
如果你问我推荐哪种方案,我会毫不犹豫地选择第三种。为什么?
- 设计更清晰:明确表达了闭包需要操作的对象
- 更符合 Rust 哲学:所有权关系一目了然
- 代码更安全:没有使用 unsafe 代码
- 性能更好:避免了额外的运行时开销
最后最后
其实这个问题反映了一个更深层的设计理念:
在 Rust 中,我们应该顺应语言的特性来设计 API,而不是硬搬其他语言的模式。就像古人说的:顺势而为,事半功倍。
所以下次当你遇到类似问题时,不妨先问问自己:是不是可以换个角度思考这个问题?也许答案就转角处等着你。在 Rust 中,最优雅的解决方案往往不是强行模仿其他语言的模式,而是遵循 Rust 的独特哲学。
如果这篇文章对你有帮助,别忘了点个~ 我是旷野,探索无尽技术!