Skip to content

User-friendly input macros. #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions 0000-scan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
- Start Date:
- RFC PR #:
- Rust Issue #:

# Summary

Add `scan!`, `scanln!`, etc. macros which mirror `print!`, `println!`, etc. for
doing straightforward input from stdin, a file, or other Reader.

# Motivation

There is no easy way in Rust to read data from files or stdin. The current
solution is to get a `Reader`, read some bytes and call the appropriate methods
to parse them into data. Whilst fine as a basis for large applications, for
tutorials or small applications (two use cases are university assignments and
programming competitions such as Google CodeJam) this is too clunky and long-
winded. We need something like `scanf` or C++ streaming io to allow easy reading
of data into Rust data structures.

# Detailed design

I propose adding four user facing macros: `scan!`, `scanln!`, `read!`, and
`readln!`. The `ln` variations expect input terminated by a newline and are
otherwise the same as the other variations. `read!` takes an additional
parameter - a `Reader` to use as a source, `scan!` uses stdin. From now on I'll
describe only `scanln!`, the other macros are identical other than the source
and terminating new line.

The syntax of `scanln!` mirrors, and is a syntactic subset of `println!`. It
would take a string literal and a number of expressions. The string literal
would be similar to a format string, but uses only a subset of the mini-
language. The expressions must all evaluate to lvalues.

The semantics of the macro is that text in the 'format' string must be matched
in the input, and holes in the format string cause the corresponding lvalue in
the argument list to be filled with the parsed value.

Some examples:

```
let mut x = 0;
let mut y = 0;
scanln!("{} {}", x, y);
```

Would parse "3 4" from stdin so that `x` is 3 and `y` is 4.


```
let mut name = StrBuf::new();
let mut age = 0;
scanln!("{}: {}", name, age);
```

Would parse "Bob: 36" from stdin so that `name` is Bob and `age` is 36.

Any data structure which implements `FromStr` could be used as a receiving
argument. This would preserve modularity.

Various features of the format string mini-language could be used. For example,
specifying a width would mean reading only those characters. Named arguments
would be supported. Using formatting information would guide parsing, for
example, `{:8x}` would read eight characters and attempt to parse them as
hexadecimal into an integer.

There's a fair bit of detail to work out about exactly what subset to use - it
is not clear to me if specifying padding is useful, or how (or if) to deal with
methods for internationalisation, etc.

Note that a format string such as `{}{}` would have to be disallowed since it
could not be unambiguously parsed. We must require some string literal between
non-fixed sized holes.

# Drawbacks

Might encourage people to use this simple UI for tasks its not suited to, such
as a full lexer/parser or general input.

# Alternatives

An unsafe scanf is not really an option for Rust.

We could do something more like C++'s streaming io, but that doesn't pair very
well with `println!` etc.

We could have something like `scan!` but using an API totally different from
`print!`.

Do nothing.

# Unresolved questions

What exactly should the mini-language look like?

How to handle failure? Should we have two versions of each macro, one which
returns a result and one which just fails? Or is just the former?