Rust 中类似 Golang 的延迟
Golang-like defer in Rust
在Go中,可以使用defer
关键字在当前函数returns时执行一个函数,类似于其他语言中传统的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,但在我的具体情况下,状态在外部系统中。我正在编写一个将键写入键值存储的测试,我需要确保它在测试结束时被删除,无论测试中的断言是否导致恐慌。
我找到了 this Gist,但我不知道这是否是推荐的方法。不安全的析构函数令人担忧。
在 Rust GitHub 存储库中也有 this issue,但它已有三年历史,显然不再相关。
(e: 不要错过下面 bluss 的回答和他们的 scopedguard 箱子。)
实现此目的的正确方法是让代码在析构函数中运行,就像您 link 执行的 defer!
宏一样。除了临时测试之外,我建议编写一个带有适当析构函数的句柄类型,例如一个通过其 MutexGuard
类型(由 lock
返回)与 std::sync::Mutex
交互:不需要在互斥量本身上调用 unlock
。 (显式 handle-with-destructor 方法也更灵活:它具有对数据的可变访问权限,而由于 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
("token tree") 的使用允许在有多个语句时调用它而无需内部 { ... }
(即它的行为更像 "normal" 控制流结构):
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);
}
(与原始输出相同。)
我将以下内容用于范围保护。它使用 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.
模块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)
}
}
这就是箱子 scopeguard on crates.io.
在Go中,可以使用defer
关键字在当前函数returns时执行一个函数,类似于其他语言中传统的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,但在我的具体情况下,状态在外部系统中。我正在编写一个将键写入键值存储的测试,我需要确保它在测试结束时被删除,无论测试中的断言是否导致恐慌。
我找到了 this Gist,但我不知道这是否是推荐的方法。不安全的析构函数令人担忧。
在 Rust GitHub 存储库中也有 this issue,但它已有三年历史,显然不再相关。
(e: 不要错过下面 bluss 的回答和他们的 scopedguard 箱子。)
实现此目的的正确方法是让代码在析构函数中运行,就像您 link 执行的 defer!
宏一样。除了临时测试之外,我建议编写一个带有适当析构函数的句柄类型,例如一个通过其 MutexGuard
类型(由 lock
返回)与 std::sync::Mutex
交互:不需要在互斥量本身上调用 unlock
。 (显式 handle-with-destructor 方法也更灵活:它具有对数据的可变访问权限,而由于 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
("token tree") 的使用允许在有多个语句时调用它而无需内部 { ... }
(即它的行为更像 "normal" 控制流结构):
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);
}
(与原始输出相同。)
我将以下内容用于范围保护。它使用 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.
模块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)
}
}
这就是箱子 scopeguard on crates.io.