rust 如何检查stdout的内容而不触发移动错误?

pkwftd7m  于 2023-05-22  发布在  其他
关注(0)|答案(1)|浏览(163)

在下面的代码中,我向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 Playground

rkkpypqq

rkkpypqq1#

你的问题是所有权编译器抱怨你的代码是正确的。
在编程过程中,你必须知道变量的位置。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

当然,你选择哪一个取决于你的使用情况。
从你提供的小例子中,我个人会选择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。如果这不是你想要的行为,那么你必须走另一条路。

相关问题