Skip to content

ACP: Stdin::next_line and BufRead::next_line #460

Open
@theemathas

Description

Proposal

Problem statement

Stdin::read_line is very commonly used in programs written by beginners in rust. (For example, see the guessing game program in the Book.) Therefore, I think that it should be as easy to use as possible.

Code using read_line tends to be more verbose than similar code in other languages (because you need to manually create an empty string yourself before using it). Furthermore, it can be error-prone because read_line returns the string including the trailing newline, tripping up many beginners. (This issue comes up pretty frequently in the rust community discord.) And if you call read_line in a loop, you need to manually clear the string yourself in order to get the correct strings. This is a lot of complexity to avoid allocations, even though interactive CLI programs are bottlenecked by the human typing the input, so allocations often don't matter.

There exists the Stdin::lines method. Calling .next() on the resultant iterator produces Option<io::Result<String>>. This requires some finangling to properly handle errors. Additionally, it is not very discoverable, and it is not intuitive that doing user input would be done with iterators.

Similar comments also apply to BufRead.

Solution sketch

I propose adding the following methods:

impl Stdin {
    pub fn next_line(&self) -> io::Result<String>;
}

pub trait BufRead {
    // existing methods omitted
    
    fn next_line(&mut self) -> io::Result<String> { .... }
}

The methods would behave as follows:

  1. The input is read into a buffer until a newline (a 0xA byte) is reached or EOF is reached, whichever is first.
  2. If EOF is reached without any bytes being read, return an Err with ErrorKind::UnexpectedEof.
  3. If the buffer ends with 0xD,0xA, or ends with 0xA, trim those bytes.
  4. Turn the buffer into a String, returning an Err with ErrorKind::InvalidData if it's not valid UTF-8.
  5. Return the resulting String.

This behavior is the same as .lines().next(), except that instead of returning None to indicate EOF, an error is returned instead.

Alternatives

  • The status quo, which would encourage users to avoid allocations at the cost of potentially unnecessary complexity.
  • The return value could be of type io::Result<Option<String>>, with the exact same behavior as .lines().next().transpose(). I think that handling the error conditions properly is too cumbersome if this return type is used.

Links and related work

Prior discussion: https://internals.rust-lang.org/t/read-line-is-a-beginner-footgun-how-to-fix-it/20663

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions