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(/* ... */);
}