Search code examples
rust

Why does StdinLock/StdoutLock need a lifetime annotation?


// /std/src/io/stdio.rs

pub struct StdinLock<'a> {
    inner: MutexGuard<'a, BufReader<StdinRaw>>,
}

impl Stdin {

    pub fn lock(&self) -> StdinLock<'static> {
        // Locks this handle with 'static lifetime. This depends on the
        // implementation detail that the underlying `Mutex` is static.
        StdinLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) }
    }

}

Since we only have one stdin instance in a process, why does not Stdin::lock just return a owned StdinLock with MutexGuard<'static, ...> instead?


Solution

  • StdinLock's lifetime parameter exists for historical reasons, and removing it would be a breaking change. As detailed in the Announcing Rust 1.61.0 blog post:

    Static handles for locked stdio

    The three standard I/O streams -- Stdin, Stdout, and Stderr -- each have a lock(&self) to allow more control over synchronizing read and writes. However, they returned lock guards with a lifetime borrowed from &self, so they were limited to the scope of the original handle. This was determined to be an unnecessary limitation, since the underlying locks were actually in static storage, so now the guards are returned with a 'static lifetime, disconnected from the handle.

    For example, a common error came from trying to get a handle and lock it in one statement:

    // error[E0716]: temporary value dropped while borrowed
    let out = std::io::stdout().lock();
    //        ^^^^^^^^^^^^^^^^^       - temporary value is freed at the end of this statement
    //        |
    //        creates a temporary which is freed while still in use
    

    Now the lock guard is 'static, not borrowing from that temporary, so this works!