在Go中,您可以使用defer关键字在当前函数返回时执行函数,类似于finally其他语言中的传统关键字.无论整个功能体发生什么,这对于清理状态都很有用.以下是Go博客的一个例子:
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
Run Code Online (Sandbox Code Playgroud)
如何在Rust中实现此功能?我知道RAII,但在我的具体情况下,状态是在外部系统中.我正在编写一个将密钥写入键值存储的测试,我需要确保在测试结束时将其删除,无论测试中的断言是否引起恐慌.
我发现了这个要点,但我不知道这是否是推荐的方法.不安全的析构函数令人担忧.
Rust GitHub存储库中也存在这个问题,但它已经有三年了,显然不再那么相关了.
huo*_*uon 23
(e:不要错过bluss的回答和他们的scopedguard箱子,下面.)
通过在析构函数中运行代码(如defer!链接的宏)来实现此目的的正确方法.对于除临时测试之外的任何其他内容,我建议使用正确的析构函数编写句柄类型,例如,std::sync::Mutex通过其MutexGuard类型(返回lock)进行交互:不需要调用unlock互斥锁本身.(显式句柄与析构函数方法也更灵活:它具有对数据的可变访问权限,而延迟方法可能无法实现,因为Rust具有强大的别名控制.)
在任何情况下,由于最近的变化,特别是pnkfelix的声音通用掉落工作,现在(很多!)改进了宏,这消除了必要性#[unsafe_destructor].直接更新将是:
struct ScopeCall<F: FnMut()> {
c: F
}
impl<F: FnMut()> Drop for ScopeCall<F> {
fn drop(&mut self) {
(self.c)();
}
}
macro_rules! defer {
($e:expr) => (
let _scope_call = ScopeCall { c: || -> () { $e; } };
)
}
fn main() {
let x = 42u8;
defer!(println!("defer 1"));
defer!({
println!("defer 2");
println!("inside defer {}", x)
});
println!("normal execution {}", x);
}
Run Code Online (Sandbox Code Playgroud)
输出:
normal execution 42
defer 2
inside defer 42
defer 1
Run Code Online (Sandbox Code Playgroud)
虽然,它在语法上会更好:
macro_rules! expr { ($e: expr) => { $e } } // tt hack
macro_rules! defer {
($($data: tt)*) => (
let _scope_call = ScopeCall {
c: || -> () { expr!({ $($data)* }) }
};
)
}
Run Code Online (Sandbox Code Playgroud)
(tt hack由于#5846,这是必要的.)
当存在多个语句时,使用泛型tt("标记树")允许人们在没有内部的情况下调用它{ ... }(即它的行为更像是"普通"控制流结构):
defer! {
println!("defer 2");
println!("inside defer {}", x)
}
Run Code Online (Sandbox Code Playgroud)
此外,为了最大程度地灵活地了解延迟代码可以对捕获的变量执行的操作,可以使用FnOnce而不是FnMut:
struct ScopeCall<F: FnOnce()> {
c: Option<F>
}
impl<F: FnOnce()> Drop for ScopeCall<F> {
fn drop(&mut self) {
self.c.take().unwrap()()
}
}
Run Code Online (Sandbox Code Playgroud)
这也需要构建ScopeCall带有Some值的值c.在Option因为调用一个舞蹈需要FnOnce移动的所有权,这从背后是不可能self: &mut ScopeCall<F>没有它.(这样做没关系,因为析构函数只执行一次.)
总而言之:
struct ScopeCall<F: FnOnce()> {
c: Option<F>
}
impl<F: FnOnce()> Drop for ScopeCall<F> {
fn drop(&mut self) {
self.c.take().unwrap()()
}
}
macro_rules! expr { ($e: expr) => { $e } } // tt hack
macro_rules! defer {
($($data: tt)*) => (
let _scope_call = ScopeCall {
c: Some(|| -> () { expr!({ $($data)* }) })
};
)
}
fn main() {
let x = 42u8;
defer!(println!("defer 1"));
defer! {
println!("defer 2");
println!("inside defer {}", x)
}
println!("normal execution {}", x);
}
Run Code Online (Sandbox Code Playgroud)
(与原始输出相同.)
blu*_*uss 11
我使用以下内容作为范围保护.它使用Deref特征来提供对保护值的共享和可变访问,而不会将其移出(这会使警卫无效!)
我的用例是在程序退出时正确地重置终端,即使恐慌:
extern crate scopeguard;
use scopeguard::guard;
// ... terminal setup omitted ...
// Use a scope guard to restore terminal settings on quit/panic
let mut tty = guard(tty, |tty| {
// ... I use tty.write() here too ...
ts::tcsetattr(tty.as_raw_fd(), ts::TCSANOW, &old_attr).ok();
});
game_main(&mut tty).unwrap(); // Deref coercion magic hands off the inner &mut TTY pointer here.
Run Code Online (Sandbox Code Playgroud)
模块scopeguard.rs:
use std::ops::{Deref, DerefMut};
pub struct Guard<T, F> where
F: FnMut(&mut T)
{
__dropfn: F,
__value: T,
}
pub fn guard<T, F>(v: T, dropfn: F) -> Guard<T, F> where
F: FnMut(&mut T)
{
Guard{__value: v, __dropfn: dropfn}
}
impl<T, F> Deref for Guard<T, F> where
F: FnMut(&mut T)
{
type Target = T;
fn deref(&self) -> &T
{
&self.__value
}
}
impl<T, F> DerefMut for Guard<T, F> where
F: FnMut(&mut T)
{
fn deref_mut(&mut self) -> &mut T
{
&mut self.__value
}
}
impl<T, F> Drop for Guard<T, F> where
F: FnMut(&mut T)
{
fn drop(&mut self) {
(self.__dropfn)(&mut self.__value)
}
}
Run Code Online (Sandbox Code Playgroud)
现在这是crates.io上的crate scopeguard.
| 归档时间: |
|
| 查看次数: |
3534 次 |
| 最近记录: |