Golang中的Golang-like延迟

Jim*_*dra 16 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)
}
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.

  • 我希望我能接受这两个答案,因为这真的很棒,而且很可能我会实际使用。谢谢! (2认同)