Refactoring a two seperate ChildStd{out, err} into a function that can do both

I have the following code that opens a child process and runs two threads to capture it's stdout and stderr. I am relatively new to rust and attempted to refactor it into a function that accepts the output buffer and the stdout/stderr captured from the process (I tried passing it in as something that impl Read + Send but I was running in to a bunch of compile errors and lifetime errors. I was wondering if the community had any tips/guidance when it comes to refactoring code like this (especially for objects that don't implement Copy).

pub struct RunningApp {
    pub cmd: String,
    pub args: Option<&'static [&'static str]>,
    pub output: Vec<Arc<Mutex<Vec<String>>>>,
    pub output_handle: Option<JoinHandle<Result<(), Error>>>,
    pub error_handle: Option<JoinHandle<Result<(), Error>>>,
    pub input_handle: Option<JoinHandle<Result<(), Error>>>,
    pub running: Arc<AtomicBool>,
    pub child: Option<Child>,
}

impl<'a> Running App {
    pub fn start(&'a mut self) -> io::Result<bool> {
        let cloned_command = self.cmd.clone();
        let cloned_args = self.args.clone();

        let cloned_stdout = self.output[0].clone();
        let cloned_stderr = self.output[1].clone();

        let mut child = Command::new(cloned_command)
            .args(cloned_args.unwrap_or(&[]))
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()?;

        // stdout
        let mut stdout = child
            .stdout
            .take()
            .ok_or_else(|| Error::new(ErrorKind::Other, "Could not capture standard stdout."))?;

        let atomic_copy = Arc::clone(&self.running);
        self.output_handle = Some(thread::spawn(move || -> Result<(), _> {
            atomic_copy.store(true, std::sync::atomic::Ordering::Relaxed);

            while atomic_copy.load(std::sync::atomic::Ordering::Relaxed) {
                let reader = BufReader::new(&mut stdout);
                reader
                    .lines()
                    .filter_map(|line| line.ok())
                    .for_each(|line| {
                        cloned_stdout.lock().unwrap().push(line);
                    });
            }
            Ok(())
        }));

        // stderr
        let mut stderr = child
            .stderr
            .take()
            .ok_or_else(|| Error::new(ErrorKind::Other, "Could not capture stderr"))?;
        let atomic_copy_2 = Arc::clone(&self.running);
        self.error_handle = Some(thread::spawn(move || -> Result<(), _> {
            atomic_copy_2.store(true, std::sync::atomic::Ordering::Relaxed);

            while atomic_copy_2.load(std::sync::atomic::Ordering::Relaxed) {
                let reader = BufReader::new(&mut stderr);
                reader
                    .lines()
                    .filter_map(|line| line.ok())
                    .for_each(|line| {
                        cloned_stderr.lock().unwrap().push(line);
                    });
            }
            Ok(())
        }));

        self.child = Some(child);

        Ok(true)
    }

}

What I want is essentially:


fn start_capture_thread(&'a mut self, /*a copy of the output buffer belonging to the struct*/, /* some generic type that represents both handles for stdout and stderr */) {
     thread::spawn(/* ... */);
}

both ChildStdout and ChildStderr are wrappers for the underlying os file(pipe) descriptors, they don't have inherent methods, but all operations are defined through trait methods.

so you can just use trait object as parameter. in your example, I think &dyn Read would be ok.

looking at your code, all you do with the handles is to create a BufReader out of them, so you can just take BufReader<R> as parameter too.

It's not clear to me what the goal is. Are you trying to combine stdout and stderr into one stream? That's possible on unix at least with some messing about (and possible line tearing), but there's no safe way in std yet.

mio based example.

No, I just want to avoid code duplication here. I am learning rust and running into issues with this (what should be) simple task. The part that opens threads for stdout and stderr are completely identical and I want to find a way to avoid that.

Thanks I'll give this a shot :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.