Struct field is freed too early

Hey guys,
My question is probably a duplicate. If it is please direct me to a helpful question because I don't have any idea what to look for.

I am currently developing a library which interfaces with a process via stdin. I'm storing the process handle in a struct:

pub struct Process {
    process: Child,
    init: bool
}

I have the following 2 functions which 'talk' to the process:

    pub fn init_engine(&mut self, protocol: String, depth: String) {
       let mut output = Stockfish::talk_to_engine(self, &format!("go depth {}", &depth));

       print!("{}", Stockfish::parse_engine_answer(&mut output));
       self.init = true;
    }

    fn talk_to_engine(&mut self, input: &String) -> ChildStdout {
        let stdin = &mut self.process.stdin.take().unwrap();
        stdin.write_all(input.as_bytes()).expect("Failed to write to process.");

         match self.process.stdout.take() {
            Some(x) => return x,
            None => panic!("response from engine was empty")
        };
    }

When I call init_engine one time, I get the correct output. However, when I call it a second time, the unwrap line panic's because it's none, so I assume the whole Process.process object does not exist anymore.

Thanks for the help. I'm still a complete beginner in this language so you can kill me on that :slight_smile:

the Option::take method removes the value from the option, leaving None in it's place.

Ah, that makes sense. But I do need the take function to get it's stdout output, right? Do I need to use a different function so it won't be None?

no, you don't need the take method here. I suspect you want .as_mut().unwrap() in order to get a mutable reference to the streams.

You will probably have to rework your function to not return ChildStdout (it could return &mut ChildStdout I believe)

Awesome, thanks for the help :smiley:

1 Like

I've reworked the method to return a &mut ChildStdout:


    fn talk_to_engine(&mut self, input: &String) -> &mut ChildStdout {
        let stdin = &mut self.process.stdin.as_mut().unwrap();
        stdin.write_all(input.as_bytes()).expect("Failed to write to process.");

         match self.process.stdout.as_mut() {
            Some(x) => return x,
            None => panic!("response from engine was empty")
        };
    }

Yet when I call the function, my program seems to hang on a blinking cursor..?

Inter process communication can be tricky, I might try to set up a mock so you can see what data is going between the two processes.

A shot in the dark guess: does the subprocess expect the messages to be delimited by a newline? It doesn't look like you are sending one.

A newline? I don't think so. I can call the subprocess in bash by myself without it and it works fine.

Talking about mocking, how would I do that?

when running the subprocess in bash, do you not press enter after typing the commands?

Well, yeah haha. That was a dumb statement from me. I thought you mean an explicit newline (\n) after the commands. What I thought was weird, in the previous version of my program I would actually get output. The problem is that when I would give it multiple commands. Isn't it deadlocking?

Also, I tried adding an explicit newline after the command. It didn't do anything. :confused:

There are two cases:

  • The program prints to stdout before stdin is closed. In this case you have to ensure that you read from stdout as the program will pause when the stdout kernel buffer is full. If you then try to write to it's stdin and you fill the stdin kernel buffer, you get a deadlock.
  • The program waits until stdin is closed before it outputs anything. In this case you have to make sure that you close stdin by dropping it. Eg self.process.stdin.take();.

I've used stdin.take() before, wouldnt it replace it with None again?
stdin.take() does what I want though.

Based on your first question, I assumed you were intending for some back-and-forth communication with the subprocess. If you only want to write to stdin once, then .take() is appropriate.

If you do need the back and forth communication, then you can't close stdin before you're done with it.

If it was working for a single command when you were closing stdin, then chances are you just need to call .flush() on the stdin, to ensure the data gets sent to the subprocess "now". I suspect you will also need an explicit newline, as neither the format! macro or write_all will include one for you.

I do need back and forth comms so I don't want to close stdin.
I've updated my function like so:

 fn talk_to_engine(&mut self, input: &String) -> String {
        let stdin = &mut self.process.stdin.as_mut().unwrap();
        stdin.write_all(input.as_bytes()).expect("Failed to write to process.");

        match stdin.flush() {
            Ok(_) => (),
            Err(e) => panic!("Unable to flush stdin buffer due to: {}", e)
        };

        let mut s = String::new();
        return match self.process.stdout.as_mut().unwrap().read_to_string(&mut s) {
            Err(why) => panic!("stdout error: {}", why),
            Ok(_) => s
        }
    }

I've also added a \r\n at the end of my command, yet it still hangs. :confused:

Oh, you're using Read::read_to_string there, which is documented as

Read all bytes until EOF in this source

That means it will pause until the child exits. You likely need to wrap stdout in a BufReader so you can use BufRead::read_line.

Yeah! that's it. I changed my function to:

let mut reader = BufReader::new(self.process.stdout.as_mut().unwrap()); , and it works!

Thank you guys!

1 Like

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.