我想制作一个类似 python 用法的上下文管理器。
我有一个套接字,我可以从中读取和写入。然后,一个设备结构体,它保存套接字,通过套接字命令设备
pub struct MyDevice{
    socket: MySocket,
    /* ... */
}
impl MyDevice{
    pub fn new()->MyDevice { todo!() }
    pub fn command(&mut self, s:&str)->std::io::Result<String>{ 
        self.socket.write(s)?; 
        self.socket.read()
    }
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)
正常的操作看起来像这样
let mut device = MyDevice::new();
device.command("Push setting A")?;
device.command("Perform task 1")?;
device.command("Pop setting A")?;
    
device.command("Perform task 2")?;
device.command("Push setting A")?;
device.command("Perform task 3")?;
device.command("Pop setting A")?; //<-- notice how setting A is load and unload multiple times
Run Code Online (Sandbox Code Playgroud)
我怎样才能像Python一样创建一个上下文管理器
    some pseudo code of what I wanted
    let setting_a : ContextManager = ContextManager::new(
        enter : ||{
            device.command("Push setting A");
        },
        exit: || {
            device.command("Pop setting A");
        }
    )
    with setting_a {
        device.command("Perform task 1");
    }
    device.command("Perform task 2");
    with setting_a {
        device.command("Perform task 3");
    }
    
    with setting_a{
        device.command("Perform task 4");
        with setting_b{
            device.command("Perform task 5");
        }
        device.command("Perform task 6");
    }
Run Code Online (Sandbox Code Playgroud)
这是我的尝试,它有点适用于一些 rc/refcell 魔法,但它没有考虑到需要执行某些任务的上下文。
impl MyDevice{
    /* ... */
    pub fn with_param<F,O>(&mut self, param:Param,f:F)->O
    where
        F: Fn()->O
    {
        let last_param:Param = self.param.clone();
        self.set_param(param);
        let o = f();
        self.set_param(last_param);
        o
    }
}
device.with_param(param, ||{/* ... */})
// v ----------- psuedo code  ------------------------------ v
let setting_a : ContextManager = ContextManager::new(
        enter : ||{
            device.command("Push setting A");
            device.command("Perform some task a-1");
        },
        exit: || {
            device.command("Pop setting A");
            device.command("Perform some task a-2");
        }
    )
// this doesn't not work on the with_param function, 
// since it not only have to set param, 
// but also need to perform some sub-task to enter and exit the context.
Run Code Online (Sandbox Code Playgroud)
1.组织此类代码的最明智的方式是什么?好吗Rc<refCell<_>>?macro?
2.我希望它是panic-proof的,我知道使用可能会引起恐慌,在这种情况下refCell我可以避免使用吗?refCell
3.如果我将来需要在线程之间传递这个怎么办?以及如何对其进行异步证明?
Cafce25 对高级函数的建议是一个不错的选择,并且在 Rust 的某些地方使用(例如作用域线程),但上下文管理器不存在且没有必要的原因主要是它们是RAII的次等兄弟Guards,这是 Rust 所青睐的,你可以在以下位置看到:
在这种情况下,您可以有一个settings方法(或每个设置的方法),该方法将在调用时推送设置,返回一个SettingsGuard在放置时弹出相同设置的方法。
为了使借用工作,并且为了安全和清晰起见,您可能希望SettingsGuard对设备有独占(/ unique / mut)引用,并允许通过自身执行命令(可能还有嵌套设置),产生类似以下内容:
dev.command("thing");
{    
    let mut g = dev.setting("g");
    g.command("foo");
    
    let mut stacked = g.setting("x");
    stacked.command("bar");
    drop(stacked);
    
    g.command("x");
}
dev.command("bob");
Run Code Online (Sandbox Code Playgroud)
这样,只要推送一个设置,设备就会通过该设置的保护进行交互,这样命名(并证明命名的合理性)更容易,并且执行上下文更清晰(编译器将阻止通过父上下文发送命令,如只要守卫还活着)。
正如您在示例中所看到的,通过显式删除相应的保护(使用类似的东西drop(g);)或块的末尾(以这种方式使用匿名块是相对正常的),可以“弹出”设置。