Как прочитать пользовательский ввод в Rust?

Примечание редактора: этот вопрос относится к частям ржавчины, которые предшествуют ржавчине 1.0, но общая концепция по-прежнему действительна в Rust 1.0.

Я намерен сделать токенизатор. Мне нужно прочитать каждую строку типа пользователя и остановить чтение, как только пользователь нажимает ctrl-D.

Я искал вокруг и нашел только пример на Rust IO, который даже не компилируется. Я посмотрел на модуль и обнаружил, что

8 ответов


Примечание редактора: этот ответ предшествует Rust 1.0. См. другие ответы для современных решений.

в Rust 0.4, используйте ReaderUtil черта к


ржавчина 1.x (см. документация):

use std::io;
use std::io::prelude::*;

fn main() {
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        println!("{}", line.unwrap());
    }
}

Руст 0.10–0.12 (см. документация):

use std::io;

fn main() {
    for line in io::stdin().lines() {
        print!("{}", line.unwrap());
    }
}

ржавчине 0.9 (см. документация 0.9):

use std::io;
use std::io::buffered::BufferedReader;

fn main() {
    let mut reader = BufferedReader::new(io::stdin());
    for line in reader.lines() {
        print(line);
    }
}

Руст 0.8:

use std::io;

fn main() {
    let lines = io::stdin().read_lines();
    for line in lines.iter() {
        println(*line);
    }
}

Руст 0.7:

use std::io;

fn main() {
    let lines = io::stdin().read_lines();
    for lines.iter().advance |line| {
        println(*line);
    }
}

по состоянию на 17 апреля 2015 от mdcox на Mozilla rust irc.

use std::io;

fn main() {
    let mut stdin = io::stdin();
    let input = &mut String::new();

    loop {
        input.clear();
        stdin.read_line(input);
        println!("{}", input);
    }
}

вопрос в том, чтобы прочитать строки из stdin и вернуть вектор. На Ржавчину 1.7:

fn readlines() -> Vec<String> {
    use std::io::prelude::*;
    let stdin = std::io::stdin();
    let v = stdin.lock().lines().map(|x| x.unwrap()).collect();
    v
}

Примечание редактора: этот ответ предшествует Rust 1.0. См. другие ответы для современных решений.

эээ... После многих проб и ошибок, я нашел решение.

Я все равно хотел бы увидеть лучшее решение, поэтому я не собираюсь принимать свое собственное решение.

код ниже печатает именно то, что пользователь вводит.

mod tokenizer {

pub fn read () -> ~[int] {
    let reader = io::stdin();
    let mut bytes: ~[int] = ~[];

    loop {
        let byte: int = reader.read_byte();
        if byte < 0 {
            return bytes;
        }
        bytes += [byte];
    }
}

}

fn main () {
    let bytes: ~[int] = tokenizer::read();
    for bytes.each |byte| {
        io::print(#fmt("%c", *byte as char));
    }
}

это то, что я придумал (с некоторой помощью от дружелюбных людей в канале # rust IRC на irc.mozilla.org):

use core::io::ReaderUtil;

fn read_lines() -> ~[~str] {
    let mut all_lines = ~[];

    for io::stdin().each_line |line| {
        // each_line provides us with borrowed pointers, but we want to put
        // them in our vector, so we need to *own* the lines
        all_lines.push(line.to_owned());
    }

    all_lines
}

fn main() {
    let all_lines = read_lines();

    for all_lines.eachi |i, &line| {
        io::println(fmt!("Line #%u: %s", i + 1, line));
    }
}

и доказательство того, что он работает:)

$ rustc readlines.rs
$ echo -en 'this\nis\na\ntest' | ./readlines
Line #1: this
Line #2: is
Line #3: a
Line #4: test

есть несколько способов, которые я могу придумать.

прочитайте все входные данные в single String

let mut input = String::new();
io::stdin().read_to_end(&mut input);

читать строки в Vector. Этот не panic при чтении строки не удается, вместо этого он пропускает, что не линия.

let stdin = io::stdin();
let locked = stdin.lock();
let v: Vec<String> = locked.lines().filter_map(|line| line.ok()).collect();

кроме того, если вы хотите, чтобы разобрать его:

после прочтения его в строку этого. Вы можете разобрать его на другие коллекции, которые реализуют FromIterator. Содержащиеся в коллекции элементы также должны реализовываться FromStr. Пока ограничение признака удовлетворяет, вы можете изменить Vec на any Collection:FromIterator, Collection<T: FromStr>

let v: Vec<i32> = "4 -42 232".split_whitespace().filter_map(|w| w.parse().ok()).collect();

также вы можете использовать его на StdinLock

let vv: Vec<Vec<i32>> = locked
    .lines()
    .filter_map(|l|
        l.ok().map(|s|
            s.split_whitespace().filter_map(|word| word.parse().ok()).collect()
        )
    )
    .collect();

в Rust 1.0 и более поздних, вы можете использовать lines способ на все, что реализует std::io::BufRead черта для получения итератора по строкам на входе. Вы также можете использовать read_line, но использование итератора более вероятно, что вы хотите. Вот версия функции в вопросе с использованием итераторов; более подробное объяснение см. ниже. (площадка ссылке)

use std::io;
use std::io::prelude::*;

pub fn read_lines() -> Vec<String> {
    let stdin = io::stdin();
    let stdin_lock = stdin.lock();
    let vec = stdin_lock.lines().filter_map(|l| l.ok()).collect();

    vec
}

а вот версия это больше похоже на версию C++ в вопросе, но на самом деле это не идиоматический способ сделать это в Rust (детская площадка):

use std::io;
use std::io::prelude::*;

pub fn read_lines() -> Vec<String> {
    let mut vec = Vec::new();
    let mut string = String::new();

    let stdin = io::stdin();
    let mut stdin_lock = stdin.lock();

    while let Ok(len) = stdin_lock.read_line(&mut string) {
        if len > 0 {
           vec.push(string);
           string = String::new();
        } else {
            break
        }
    }

    vec
}

получить что-то, что реализует BufRead, который необходимо вызвать lines() или read_line(), вы называете std::io::stdin() получить дескриптор стандартного ввода, а затем вызвать lock() в результате этого, чтобы получить эксклюзивный контроль над стандартным входным потоком (вы должны иметь эксклюзивный контроль для получения BufRead, потому что в противном случае буферизация может привести к произвольным результатам, если два потока читали из stdin сразу).

чтобы собрать результат в Vec<String> можно использовать collect метод итератора. lines() возвращает итератор над Result<String>, поэтому нам нужно обрабатывать случаи ошибок, в которых строка не может быть прочитана; для этого примера мы просто игнорируем ошибки с filter_map это просто пропускает любые ошибки.

версия C++ like использует read_line, который добавляет строку чтения к данной строке, а затем мы нажимаем строку в наш Vec. Потому что мы передаем право собственности на строку Vec когда мы это делаем, и потому read_line в противном случае продолжал бы добавлять к string, нам нужно выделить новую строку для каждого цикла (это похоже на ошибку в исходной версии C++ в вопросе, в которой одна и та же строка является общей и поэтому будет накапливать каждую строку). Мы используем while let продолжать читайте, пока мы не нажмем ошибку, и мы сломаемся, если мы когда-либо читали нулевые байты, что указывает на конец ввода.