3 个版本
0.0.3 | 2022年9月28日 |
---|---|
0.0.2 | 2022年9月28日 |
0.0.1 | 2020年7月21日 |
#363 in 无标准库
1,358 每月下载量
用于 cproxy
10KB
58 行
with_drop
Nostd 包装器,可以用来执行自定义析构函数。
用法
Cargo.toml
[dependencies]
with_drop = "0.0.3"
代码中
use std::{cell::RefCell};
use with_drop::with_drop;
let drop_sum = RefCell::new(0);
{
let mut v = with_drop(32, |x| { *drop_sum.borrow_mut() += x });
// use value
assert!(*v == 32);
// Modify it
*v = 42;
}
// Drop function should have been executed
assert!(*drop_sum.borrow() == 42);
动机
以下是一段代码示例
use std::{io::Result, process::Command, process::Stdio};
fn main() -> Result<()> {
let child1 = Command::new("echo").arg("42").stdout(Stdio::piped()).spawn()?;
let child2 = Command::new("echo").arg("23").stdout(Stdio::piped()).spawn()?;
assert!(child1.wait_with_output()?.stdout == b"42\n");
assert!(child2.wait_with_output()?.stdout == b"23\n");
Ok(())
}
简单,对吧?我们启动两个子进程,收集它们的输出并与其比较一个值。但是,这个例子并不完全正确;它不是异常安全的。`std::process::Child` 文档指定,必须 `手动调用 wait()``(Child 不实现 Drop)才能正确清理进程(否则在 Linux 下将产生 `僵尸进程`)。
现在,如果你仔细查看上面的代码示例,可能会发现并不是每条代码路径都会调用 wait;如果一切按计划进行,wait 将会被调用,但如果由于失败的结果而提前退出,wait 将不会被 child1 或 child2 调用。
这种特性称为异常安全性(或结果安全性,因为 Rust 没有异常?);上面的代码示例不是异常安全的。我们可以使用 if 语句手动捕获所有这些情况,但这会使代码非常难以管理。最佳方案是 Child 类型实现 Drop 并自动等待进程。但它没有。在这种情况下,我们可以使用 `with_drop()
` 创建一个包装器
use std::{io::Result, process::Command, process::Stdio};
use with_drop::with_drop;
fn main() -> Result<()> {
let child1 = with_drop(Command::new("echo").arg("42").stdout(Stdio::piped()).spawn()?, |mut child| {
// Explicitly ignoring errors; the command might not have been started or might
// not have ended or might have yielded an error; in any case we don't mind because
// we just care about cleaning up zombies.
let _ = child.wait();
});
let child2 = with_drop(Command::new("echo").arg("23").stdout(Stdio::piped()).spawn()?, |mut child| {
let _ = child.wait();
});
assert!(child1.into_inner().wait_with_output()?.stdout == b"42\n");
assert!(child2.into_inner().wait_with_output()?.stdout == b"23\n");
Ok(())
}
关于 `finally()
` 怎么样?
能否用类似 finally() 的结构体代替?不可以!我们的 finally() 守护者必须存储 child 变量的可变引用,这将阻止我们调用 wait_with_output(借用检查器会报错)。
use std::{io::Result, process::Command, process::Stdio};
use with_drop::with_drop;
struct Finally<F: FnMut()> {
f: F
}
impl<F: FnMut()> Drop for Finally<F> {
fn drop(&mut self) {
(self.f)();
}
}
fn finally<F: FnMut()>(f: F) -> Finally<F> {
Finally { f }
}
fn main() -> Result<()> {
let mut child1 = Command::new("echo").arg("42").stdout(Stdio::piped()).spawn()?;
let finally_guard1 = finally(|| {
let _ = child1.wait();
});
let mut child2 = Command::new("echo").arg("42").stdout(Stdio::piped()).spawn()?;
let finally_guard2 = finally(|| {
let _ = child2.wait();
});
// error[E0505]: cannot move out of `child1` because it is borrowed
//assert!(child1.wait_with_output()?.stdout == b"42\n");
//assert!(child2.wait_with_output()?.stdout == b"23\n");
Ok(())
}
测试
安装 clippy、rustfmt 和 nono
$ rustup component add rustfmt
$ rustup component add clippy
$ RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-nono
现在使用这些工具执行测试。
$ cargo build
$ cargo test
$ cargo clippy --all-targets --all-features -- -D warnings
$ cargo fmt -- --check
$ cargo nono check
许可协议
版权所有 © (C) 2020,Karolin Varner。保留所有权利。
在满足以下条件的情况下,允许重新分配和使用源代码和二进制代码,无论是否修改:
源代码的再分发必须保留上述版权声明、本许可清单以及以下免责声明。二进制形式的再分发必须在文档和/或随分发提供的其他材料中复制上述版权声明、本许可清单以及以下免责声明。未经具体事先书面许可,不得使用Karolin Varner的名字或其贡献者的名字来支持或推广由此软件派生出的产品。
本软件由版权所有者和贡献者“按原样”提供,并明确或暗示地放弃了任何明示或默示的保证,包括但不限于适销性和特定用途的适用性保证。在任何情况下,Softwear, BV不对任何直接、间接、偶然、特殊、示范性或后果性的损害(包括但不限于替代货物或服务的采购;使用、数据或利润的损失;或业务中断)承担责任,无论这些损害是由于何种原因引起的,无论基于何种责任理论(合同责任、严格责任或侵权责任,包括疏忽或其他),即使被告知了此类损害的可能性。