在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)
}

如何在Rust中实现这一功能?我知道RAII,但在我的具体情况下,该州处于外部系统中.我正在编写一个将密钥写入键值存储的测试,我需要确保在测试结束时将其删除,而不管测试中的断言是否会导致panic .

我找到了this Gist个,但我不知道这是不是推荐的方法.不安全的析构函数令人担忧.

Rust GitHub存储库上也有this issue个,但它已经有三年的历史了,显然已经不是很重要了.

推荐答案

(e:不要错过布拉斯的答案和他们下面的scopedguard箱.)

实现这一点的正确方法是让代码在析构函数中运行,就像您链接到的defer!宏所做的那样.对于任何比即席测试更多的测试,我建议使用适当的析构函数编写句柄类型,例如,通过其MutexGuard类型(由lock返回)与std::sync::Mutex交互:不需要在互斥体本身上调用unlock.(显式句柄与析构函数方法也更灵活:它可以可变地访问数据,而延迟方法可能不能,因为Rust的强大别名控制.)

无论如何,这个宏现在(很多!)由于最近的变化而有所改善,尤其是pnkfelix的sound generic drop work,它消除了#[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);
}

输出:

normal execution 42
defer 2
inside defer 42
defer 1

不过,它在语法上会更好,如下所示:

macro_rules! expr { ($e: expr) => { $e } } // tt hack
macro_rules! defer {
    ($($data: tt)*) => (
        let _scope_call = ScopeCall { 
            c: || -> () { expr!({ $($data)* }) }
        };
    )
}

(由于#5846,tt hack是必需的.)

通用tt("令牌树")的使用允许当有多个语句(即,它的行为更像"正常"的控制流 struct )时在没有内部{ ... }的情况下调用它:

defer! {
    println!("defer 2");
    println!("inside defer {}", x)
}

此外,为了最大限度地提高延迟代码处理捕获变量的灵活性,可以使用FnOnce而不是FnMut:

struct ScopeCall<F: FnOnce()> {
    c: Option<F>
}
impl<F: FnOnce()> Drop for ScopeCall<F> {
    fn drop(&mut self) {
        self.c.take().unwrap()()
    }
}

这还需要用c的值周围的Some来构造ScopeCall.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);
}

(与原始输出相同.)

Go相关问答推荐

如何在围棋中从多部分.Part中获取多部分.文件而不保存到磁盘?

Golang:访问any类型泛型上的字段

Go:如何在不加载正文的情况下创建 http 代理通行证

3 字节切片和有符号整数类型之间的转换

将 big.Int 转换为 [2]int64,反之亦然和二进制补码

当函数返回一个函数时,为什么 Go 泛型会失败?

如何在切片增长时自动将切片的新元素添加到函数参数

如何将 npm 安装进度条通过管道传输到终端?

如果 transaction.Commit 对带有 postgres 连接的 SQL 失败,您是否需要调用 transaction.RollBack

在 Go GRPC 服务器流式拦截器上修改元数据

为什么 reflect.TypeOf(new(Encoder)).Elem() != reflect.TypeOf(interfaceVariable)?

Go 错误:Is() 和 As() 声称是递归的,是否有任何类型实现错误接口并支持这种递归 - 无错误?

Go http标准库中的内存泄漏?

构造日期时使用 int 作为月份

Golang 修改没有struct的 json

如何从 golang 中的数组 unsafe.Pointer 创建数组或切片?

服务器启动时记录

使用反射获取指向值的指针

如何使用其 go 客户端在 kubernetes 服务上观看事件

Go 语言 IDE 支持的状态如何?