mia*_*lop 1 generics callback lifetime rust
(完全公开,这是我的 reddit 帖子的转发)
首先,我想声明一下,我并不是一名开发人员,而是 Rust 的初学者。我有一个可能很微不足道的问题。
我正在致力于为定制 CPU 实现模拟器。我希望能够挂钩仿真(并稍后进行调试)。我希望为 CPU、挂钩系统和模拟器提供单独的包,并且希望我的 CPU 不知道挂钩系统的实现细节,反之亦然。但我在建模时遇到了问题。
现在,我有类似的东西:
// crate CPU
pub struct Cpu {
hooks: HashMap<VirtAddr, hook::Hook>,
}
impl Cpu {
pub fn add_hook(&mut self, hook: hook::Hook) {
self.hooks.insert(hook.addr(), hook);
}
pub fn step(&mut self) {
hook::hook_before!();
}
}
// crate HOOK
pub struct Hook {
addr: VirtAddr,
callback: Box<dyn FnMut(VirtAddr) -> Result<String>>
}
impl Hook {
pub fn new(
addr: VirtAddr,
callback: impl FnMut(VirtAddr) -> Result<String> + 'static,
) -> Self {
Self {
addr,
callback: Box::new(callback),
}
}
pub fn run(&mut self, addr: VirtAddr) -> Result<String> {
(self.callback)(addr)
}
#[macro_export]
macro_rules! hook_before {
// do something
hook.run()
}
}
// crate EMU
pub struct Emu {
cpu: cpu::Cpu,
}
impl Emu {
pub fn add_hook(&mut self, hook: hook::Hook) {
self.cpu.add_hook(hook);
}
pub fn run() {
self.cpu.step();
}
}
// user's crate
fn main() {
// create emu
{
let h = hook::Hook::new(
VirtAddr(0x00016d),
// this is VERY WRONG
|addr| {
let cpu = emu.cpu();
// do stuff with the CPU
},
);
emu.add_hook(h);
}
emu.run();
}
Run Code Online (Sandbox Code Playgroud)
这不起作用,因为 rustc 告诉我,我的闭包可能比当前的 ( main) 函数寿命更长,由于'static生命周期,这是完全公平的。
这意味着我应该在Hook定义中添加生命周期,以明确通知 rustc 闭包不能比函数的生命周期长。Cpu但是然后,我必须添加 my和的定义Emu。如果我使用泛型作为闭包而不是Box<dyn>. 我也不能简单地将 theCpu作为参数传递给闭包,因为那样的话,我最终会产生循环依赖,即Cpu需要Hookrequire Cpu。我也无法使用函数指针 ( fn),因为它无法捕获其上下文,并且需要使用Cpu作为参数。
您可以说前两个解决方案很好,但我看到了多个问题:
Cpu然后就会知道太多关于什么是aHook所以,我觉得我错过了一些东西。要么是我的 Rust 技能太低,无法找到好的解决方案,要么是我的开发技能问题。无论如何,我无法弄清楚。也许我解决问题的方式全错了,我应该把一切都颠倒过来,或者也许没有好的解决方案,我将不得不坚持生命周期/泛型。
你有什么想法给我吗?也许有更适合 Rust 的设计模式?我在这里阅读了很多帖子解决方案,但似乎没有任何内容适合我的情况。
我也不能简单地将 the
Cpu作为参数传递给闭包,因为那样的话,我最终会产生循环依赖,即Cpu需要HookrequireCpu。
事实上,这是最好的解决方案。在其他语言中,可能不是,但在 Rust 中,尝试记住各个钩子函数emu将导致无法执行任何操作emu,因为它已经被借用了。一般原则是,当实体之间存在像这样的抽象循环关系时(Cpu 拥有 Hook,但 Hook 想要与 Cpu 一起工作),最好将关系中一个成员的借用传递给另一个成员,而不是传递给另一个成员。试图让它们永久地相互引用。
更改您的挂钩函数以具有类似FnMut(&mut Cpu, VirtAddr) -> Result<String>.
pub struct Hook {
pub addr: VirtAddr,
callback: Box<dyn FnMut(&mut Cpu, VirtAddr)>
}
Run Code Online (Sandbox Code Playgroud)
您仍然需要一些摆弄来满足借用检查器:对 的可变访问Cpu意味着对 的可变访问hooks,并且不允许钩子直接通过访问 Cpu 来改变自身。有几种可能的技巧可以解决这个问题;最简单的一种方法是暂时删除钩子,使其仅由函数调用拥有。(这意味着将Hook始终看到Cpu不包含该钩子的 a 。)
impl Cpu {
pub fn step(&mut self) {
let addr = VirtAddr(0x00016d); // placeholder
if let Some(mut hook) = self.hooks.remove(&addr) {
hook.run(self, addr);
self.hooks.insert(addr, hook);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是您的整个程序,进行了足够的更改以使其编译:
use std::collections::HashMap;
mod cpu {
use super::*;
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct VirtAddr(pub u32);
pub struct Cpu {
hooks: HashMap<VirtAddr, hook::Hook>,
}
impl Cpu {
pub fn new() -> Self {
Self { hooks: HashMap::new() }
}
pub fn add_hook(&mut self, hook: hook::Hook) {
self.hooks.insert(hook.addr, hook);
}
pub fn step(&mut self) {
let addr = VirtAddr(0x00016d); // placeholder
if let Some(mut hook) = self.hooks.remove(&addr) {
hook.run(self, addr);
self.hooks.insert(addr, hook);
}
}
}
}
mod hook {
use super::cpu::{Cpu, VirtAddr};
pub struct Hook {
pub addr: VirtAddr,
callback: Box<dyn FnMut(&mut Cpu, VirtAddr)>
}
impl Hook {
pub fn new(
addr: VirtAddr,
callback: impl FnMut(&mut Cpu, VirtAddr) + 'static,
) -> Self {
Self {
addr,
callback: Box::new(callback),
}
}
pub fn run(&mut self, cpu: &mut Cpu, addr: VirtAddr) {
(self.callback)(cpu, addr)
}
}
}
mod emu {
use super::*;
pub struct Emu {
cpu: cpu::Cpu,
}
impl Emu {
pub fn new() -> Self {
Self { cpu: cpu::Cpu::new() }
}
pub fn add_hook(&mut self, hook: hook::Hook) {
self.cpu.add_hook(hook);
}
pub fn run(&mut self) {
self.cpu.step();
}
}
}
fn main() {
let mut emu = emu::Emu::new();
{
let h = hook::Hook::new(
cpu::VirtAddr(0x00016d),
|_cpu, _addr| {
println!("got to hook");
},
);
emu.add_hook(h);
}
emu.run();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
636 次 |
| 最近记录: |