如何在不触发移动错误的情况下检查标准输出的内容?

问题描述 投票:0回答:1

在下面的代码中,我写了一些文本给

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 borrow-checker
1个回答
0
投票

这里的问题是所有权;编译器抱怨你的代码是正确的。

在编程过程中,您必须注意变量所在的位置。 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
。如果那不是您想要的行为,那么您必须走另一条路。

© www.soinside.com 2019 - 2024. All rights reserved.