在下面的代码中,我写了一些文本给
stdout
:
use std::{
io::Write,
sync::{Arc, Mutex},
};
pub struct CmdRunner {}
impl CmdRunner {
pub fn run<W: Write + Send + 'static>(&mut self, stdout_mutex: Arc<Mutex<Option<W>>>) {
// Code that runs the command
let stdout_clone = Arc::clone(&stdout_mutex);
std::thread::spawn(move || {
// This is wrapped in code that listens to Ctrl + C (in raw mode)
let stdout_clone = Arc::clone(&stdout_mutex);
let mut stdout_lock = stdout_clone.lock().unwrap();
stdout_lock.take();
});
let mut stdout_lock = stdout_clone.lock().unwrap();
let stdout = stdout_lock.as_mut().unwrap();
write!(stdout, "Command executed successfully!\r\n").unwrap();
}
}
fn handle_cmd_input<T: Write>(stdout: &mut T) {
write!(stdout, "Input handled!\r\n").unwrap();
}
fn handle_cmd_run<T: Write + std::marker::Send + 'static>(mut stdout: T) {
handle_cmd_input(&mut stdout);
let mut cmd_runner = CmdRunner {};
let stdout_mutex = Arc::new(Mutex::new(Some(stdout)));
cmd_runner.run(stdout_mutex);
}
fn main() {
let stdout = Vec::new();
handle_cmd_run(stdout);
let stdout_string = String::from_utf8_lossy(&stdout);
println!("stdout: {}", stdout_string);
}
如您所见,我尝试打印
stdout
,但这会产生错误:
44 | let stdout = Vec::new();
| ------ move occurs because `stdout` has type `Vec<u8>`, which does not implement the `Copy` trait
45 |
46 | handle_cmd_run(stdout);
| ------ value moved here
47 |
48 | let stdout_string = String::from_utf8_lossy(&stdout);
| ^^^^^^^ value borrowed here after move
编译器建议我写:
46 | handle_cmd_run(stdout.clone());
| ++++++++
但如果我这样做,
write!
将写入克隆的stdout
,而不是原始的stdout
。
这里的问题是所有权;编译器抱怨你的代码是正确的。
在编程过程中,您必须注意变量所在的位置。 Rust
Arc<Mutex>
的工作方式不同于 C/C++;它们不存在于变量之外,而是围绕着它。这意味着一旦您将变量移入 Arc<Mutex>
中,您将永远无法将其取回。
有了这些知识,编译器为什么抱怨
String::from_utf8_lossy(&stdout);
是有道理的 - stdout
变量现在位于互斥体内部,而原始的 stdout
变量不再存在。
有几种方法可以解决这个问题:
Arc<Mutex>
并将其用于String::from_utf8_lossy
.Arc<Mutex>
,而是从您的函数返回 stdout
数据&mut
引用而不是整个 Arc<Mutex>
恶作剧 - 这将需要 scoped threads.你选择哪一个取决于你的用例,当然。
根据您提供的小示例,我个人会选择作用域线程:
use std::io::Write;
pub struct CmdRunner {}
impl CmdRunner {
pub fn run<W: Write + Send>(&mut self, stdout: &mut W) {
// Code that runs the command
std::thread::scope(|s| {
s.spawn(|| {
// This is wrapped in code that listens to Ctrl + C (in raw mode)
write!(stdout, "Command output: 42\r\n").unwrap();
});
});
write!(stdout, "Command executed successfully!\r\n").unwrap();
}
}
fn handle_cmd_input<T: Write>(stdout: &mut T) {
write!(stdout, "Input handled!\r\n").unwrap();
}
fn handle_cmd_run<T: Write + Send>(stdout: &mut T) {
handle_cmd_input(stdout);
let mut cmd_runner = CmdRunner {};
cmd_runner.run(stdout);
}
fn main() {
let mut stdout = Vec::new();
handle_cmd_run(&mut stdout);
let stdout_string = String::from_utf8_lossy(&stdout);
println!("stdout: {}", stdout_string);
}
stdout: Input handled!
Command output: 42
Command executed successfully!
之所以可行,是因为
Arc<Mutex>
由于多线程的性质,不 是必需的;它是必需的,因为 std::thread::spawn
生成的线程具有开放式生命周期,与它们的生成范围断开连接。通过 std::thread::scope
生成它,线程的生命周期连接到父作用域,并且可以从内部使用普通引用。另请注意,move
已从闭包中删除,以防止将 stdout
变量移动到线程中。
免责声明: 我假设您忘记了
join
线程,否则您的整个代码将没有多大意义。请注意,std::thread::scope
在最后自动加入,因此您的父线程将被阻塞,直到线程完成,并且"Command executed successfully!"
只会在线程退出后添加到 stdout
。如果那不是您想要的行为,那么您必须走另一条路。