Содержание


Rust - новый язык программирования: Часть 10. Средства ввода/вывода и их использование

Comments

В предыдущих статьях цикла был сделан общий обзор языка программирования Rust, обсуждались основы синтаксиса: были описаны простейшие компоненты (переменные, простые типы данных, операторы и расширения), управляющие конструкции и составные структуры данных, функции и деструкторы. Особое внимание было уделено модели памяти в целом, концепции владения объектами, общим принципам управления памятью, использованию собственных и управляемых блоков общей памяти, а также заимствованных указателей. В данной статье рассматриваются средства ввода/вывода языка Rust.

В любом языке программирования подсистема ввода/вывода является важной составной частью. Некоторые простейшие средства работы со стандартным потоком вывода уже использовались в предыдущих статьях (print, println). Эти функции интуитивно понятны, и не вызывают каких-либо затруднений в процессе их применения. Поэтому сейчас речь пойдёт главным образом о средствах приёма данных, вводимых пользователем и об инструментах работы с файлами.

1. Модуль std::io

В Rust все средства ввода/вывода собраны в модуле io.

В трэйтах (trait) Reader и Writer определён минимальный набор самых простых методов ввода и вывода. Трэйты ReaderUtil и WriterUtil предлагают более широкий выбор методов, предоставляющих пользователю расширенные возможности управления вводом и выводом. Например, Reader позволяет лишь считывать заданное количество байтов в буфер, в то время как в ReaderUtil предлагаются методы считывания целой строки, нескольких строк, числовых значений и т.д. Кроме того, имеется реализация (implementation) ReaderUtil для <T: Reader>, которая позволяет получить доступ ко всем методам Reader.

Как уже было сказано выше, наиболее часто используемые функции print() и println() также определены в модуле io, но их явное импортирование в большинстве случаев не требуется, так как об этом уже позаботилась система компиляции и связывания.

Кроме того, здесь же определяются весьма полезные функции stdin(), stdout() и stderr(), возвращающие указатели на три стандартных дескриптора файлов: стандартный ввод, стандартный вывод и стандартный поток ошибок соответственно.

2. Получение данных от пользователя

В любом случае самым простым способом получения данных от пользователя является использование функции stdin(), создающей указатель на объект @Reader, что позволяет считывать данные из потока стандартного ввода (аналогичные функции существуют и для стандартного потока вывода - stdout(), а также для стандартного потока ошибок - stderr()). После создания указателя @Reader можно воспользоваться функцией read_line() для чтения строки, вводимой пользователем, как показано в листинге 1.

Листинг 1. Считывание данных, вводимых пользователем
fn main() {
  let stdin = std::io::stdin();
  print( "Как Вас зовут? " );
  let name_str = stdin.read_line();
  println( fmt!("Очень приятно познакомиться, %s\n", name_str) );
}

В трэйте ReaderUtil помимо простых функций чтения байтов и одиночных строк также определён богатый набор инструментов для считывания данных при различных условиях: считывание байтов до тех пор, пока не встретится признак конца файла - read_until(), считывание строки, записанной в C-стиле (с нулевым завершающим символом), - read_c_str(), считывание всего потока данных целиком - read_whole_stream(), считывание всех строк из потока данных - read_lines(), а также большой набор функций, позволяющих считывать числовые значения, как целые, так и с плавающей точкой.

3. Считывание данных из файла

Все необходимые инструменты для работы с файлами также располагаются в модуле std::io. Прежде всего следует обратить внимание на следующие функции:

fn FILE_reader(f: *libc::FILE, cleanup: bool) -> @Reader
fn FILE_writer(f: *libc::FILE, cleanup: bool) -> @Writer
fn file_reader(path: &Path) -> Result<@Reader, ~str>
fn file_writer(path: &Path, flags: &[FileFlag]) -> Result<@Writer, ~str>
fn mk_file_writer(path: &Path, flags: &[FileFlag]) -> Result<@Writer, ~str>

Варианты FILE_* читают и записывают файлы в C-стиле, тогда как варианты file_* предназначены для использования именно в стиле Rust. Поскольку первые две функции очевидно исполняют роль вспомогательных инструментов для специфических задач, а кроме того всё-таки рассматривается именно язык Rust, предпочтение следует отдать Rust-стилю. Поэтому далее рассматриваться будут только функции file_reader и file_writer. Обе функции в качестве первого параметра принимают путь к файлу, имеющий тип ~str, и обе возвращают значение типа Result. Таким образом, чтобы понять операции файлового ввода/вывода в Rust, необходимо также правильно понимать и оценивать тип Result.

3.1. Тип возвращаемого значения Result

Этот тип служит в Rust средством, позволяющим избежать ошибок времени выполнения. Result - это перечисление, содержащее следующие значения (см. src/libstd/result.rs):

pub enum Result<T, U> {
    Ok(T),  /// содержит значение при успешном возврате из функции
    Err(U)  /// содержит значение при ошибке возврата из функции
}

Значения типа Result обязательно должны быть распакованы (деструктуризованы) перед их использованием.

В примере программы будет использоваться следующая функция из модуля std::result:

fn unwrap<T, U>(res: Result<T, U>) -> T

- если получен результат Ok(T), то функция возвращает значение T, извлечённое из Ok(T), которое может использоваться далее в программе.

Разумеется, этой функцией набор инструментария библиотеки std::result не ограничивается. Описания других функций можно найти в указанной выше документации.

3.2. Пример считывания данных из файла

Исходный код примера приведён в листинге 2. В примере просто считываются байты из заданного файла, затем результат чтения преобразовывается в строку и выполняется сравнение с образцом. Предполагается, что сама программа и тестовый файл read.txt, из которого считываются данные, находятся в одном и том же каталоге.

Листинг 2. Побайтовое чтение данных из файла.
use std::io::*;
use std::vec::*;
use std::path::*;

fn is_success( fpath: &PosixPath ) {
  let maybe_test_reader: Result<@Reader, ~str> = file_reader( fpath );
  let test_reader: @Reader = std::result::unwrap( maybe_test_reader );

  let mut bytes: ~[u8] = ~[];
  loop {
    let byte: int = test_reader.read_byte();
    if test_reader.eof() { break; }
    append_one( bytes, byte as u8 );
  }

  let sample : ~str = ~"success";
  let maybe_success: ~str = std::str::from_bytes( bytes );
  if maybe_success == sample {
    println( "Операция чтения из файла завершилась успешно" );
  }
}

fn main() {
  let fpath : ~PosixPath = ~PosixPath( "./read.txt" );
  is_success( fpath );
}

В приведённом выше примере тестовая функция is_success() создаёт объект test_reader, используя переданный ей путь к файлу. Для большей наглядности содержимое файла считывается по одному байту за раз, и считанный байт добавляется в вектор bytes. Байты считываются как целые значения типа int, но при вставке очередного байта в вектор ожидается значение типа u8. Поэтому для каждого вставляемого байта выполняется явное преобразование as u8. Эти операции продолжаются до тех пор, пока при считывании не встретится конец файла (end of file).

Метод reader.eof() возвращает true, когда текущий считанный байт содержит признак eof. В этом случае значение этого байта равно -1 (именно это является причиной того, что reader.read_byte() возвращает int, а не u8), и его не нужно добавлять в вектор. Проверяется условие, и если обнаружен EOF, то происходит выход из цикла.

Проверка правильности результата считывания основывается на том факте, что считанные байты представляют собой ASCII-значения (следовательно, совпадают с соответствующей частью кодировки UTF8), то есть, символы, составляющие слово "success".

3.3. Почему не цикл while

При рассмотрении кода в листинге 2 может возникнуть резонный вопрос: почему выполняется выход из цикла с помощью проверки на eof в теле цикла, а не используется более распространённая проверка условия перед очередной итерацией? Причина в том, как работает reader.eof(). При использовании цикла while с проверкой на eof в условном выражении EOF-байт с значением -1 (255u8) добавляется в целевой вектор. Поэтому при использовании while приходится "выталкивать" самое последнее значение из вектора, как показано в листинге 3.

Листинг 3. Фрагмент считывания байтов с использованием цикла while
let mut bytes: ~[u8] = ~[];
while !reader.eof() {
  std::vec::append_one( bytes, reader.read_byte() as u8 );
}
std::vec::pop( bytes );

Разумеется, и такой вариант обработки данных имеет право на существование.

Заключение

В языке Rust практически все средства ввода/вывода сконцентрированы в модуле std::io. Для каждого элементарного типа данных предусмотрены инструменты его считывания и представления при выводе. Основными компонентами модуля std::io являются трэйты Reader, Writer, ReaderUtil и WriterUtil, предоставляющие все необходимые возможности управления вводом и выводом данных.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=951815
ArticleTitle=Rust - новый язык программирования: Часть 10. Средства ввода/вывода и их использование
publish-date=11052013