Rust 中类似 Golang 的延迟

标签 go rust

在 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但我不知道这是否是推荐的方法。不安全的析构函数令人担忧。

还有this issue在 Rust GitHub 存储库上,但它已有三年历史,显然不再非常相关。

最佳答案

(e:不要错过下面的 bluss 的回答和他们的 scopedguard crate 。)

正确的方法是让代码在析构函数中运行,例如 defer!您链接到的宏。除了临时测试之外,我建议使用适当的析构函数编写句柄类型,例如一个与 std::sync::Mutex 交互通过其 MutexGuard 类型(由 lock 返回):无需调用 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)* }) }
        };
    )
}

(tt hack 是必需的,因为 #5846 。)

使用泛型 tt ("token tree") 允许在没有内部 { ... } 的情况下调用它当有多个语句时(即它的行为更像一个“正常”的控制流结构):

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

这也需要构建 ScopeCallSome 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);
}

(与原始输出相同。)

关于Rust 中类似 Golang 的延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29963449/

相关文章:

algorithm - 如何将长字符串转换为短字符串,并且是可逆操作

go - 声明一个类型,其中该类型是一个返回 struct{} 的函数

go - unstructured.UnstructuredList 导致大量 reflect.go 跟踪

rust - 在匹配 `Result` 时使用 if-let 绑定(bind)并仍然能够捕获错误的惯用方法是什么?

null - 我们应该使用 Option 还是 ptr::null 来表示 Rust 中的空指针?

python - 如何在 Rust 中收集传感器数据并使其在 Python 中可查询/可用?

go - 在 Golang 中连续运行 io.Copy(os.Stdout, &r) 结果不同

JSON 数组编码

json - 为什么这个 JSON 解析在 Rust 的指定位置失败?

Rust:将值移出选项并压入堆栈