Skip to content

Tracking Issue for owned locked stdio handles #86845

Closed
@tlyu

Description

@tlyu

Feature gate: #![feature(stdio_locked)]

This is a tracking issue for adding owned locked handles to stdio.

Especially for beginners, using stdio handles can involve intimidating problems with locking and lifetimes. First, the user has to call a free function (stderr(), stdin(), stdout()) to get a handle on the stream; then, the user might have to call the lock() method (for example, to gain access to the lines() iterator constructor). At this point, lifetime issues rapidly arise: the following code (playground) will produce a compile error.

use std::io;
fn main() {
    let _h = io::stdin().lock();
}
error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:3:14
  |
3 |     let _h = io::stdin().lock();
  |              ^^^^^^^^^^^       - temporary value is freed at the end of this statement
  |              |
  |              creates a temporary which is freed while still in use
4 | }
  | - borrow might be used here, when `_h` is dropped and runs the destructor for type `StdinLock<'_>`
  |
  = note: consider using a `let` binding to create a longer lived value

The need to create a let binding to the handle seems confusing and frustrating, especially if the program does not need to use the handle again. The explanation is that the lock behaves as if it borrows the original handle from stdin(), and the temporary value created for the call to the lock() method is dropped at the end of the statement, invalidating the borrow. That explanation might be beyond the current level of understanding of a beginner who simply wants to write an interactive terminal program.

Even experienced Rust programmers sometimes have trouble with the lifetime management required for using locked stdio handles: (comment), (comment).

Fortunately, the stdio handles don't contain any non-static references, so it's possible to create owned locks such as StdinLock<'static>. This proposal creates methods and free functions for creating owned locked stdio handles. The methods consume self, eliminating any lifetime problems. The free functions directly return an owned locked handle, for programs where there is no need to use an unlocked handle. The implementation currently depends on the mutex references in the stdio handles being static, but this does not preclude future changes to the locking internals (for example, using Arc to wrap the mutex).

Public API

// std::io

pub fn stderr_locked() -> StderrLock<'static> { /* ... */ }
pub fn stdin_locked() -> StdinLock<'static> { /* ... */ }
pub fn stdout_locked() -> StdoutLock<'static> { /* ... */ }
impl Stderr {
    pub fn into_locked(self) -> StderrLock<'static> { /* ... */ }
}
impl Stdin {
    pub fn into_locked(self) -> StdinLock<'static> { /* ... */ }
}
impl Stdout {
    pub fn into_locked(self) -> StdoutLock<'static> { /* ... */ }
}

Steps / History

Unresolved Questions

  • During stabilization, might we want to change the documentation to encourage people to prefer the free functions that obtain owned locked handles (stdin_locked(), etc.) over the ones that obtain unlocked handles (stdin(), etc.)?

@rustbot label +A-io +D-newcomer-roadblock

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCD-newcomer-roadblockDiagnostics: Confusing error or lint; hard to understand for new users.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions