Как "готовить" Rust

translated

Эта книга Rust. Сборник рецептов является коллекцией простых примеров, которые демонстрируют хорошие практики, чтобы выполнять стандартные программистские задачи, используя библиотеки из экосистемы Rust.

Вы можете подробнее прочитать о Rust. Сборник рецептов, включая советы о том, как читать книгу, как использовать примеры и заметки о принятых соглашениях.

Соавторам

Этот проект подразумевает лёгкость вхождения для новичков в программировании на Rust, а также является одним из самых лёгких путей, чтобы быть вовлечённым в Rust-сообщество. Помощь всегда приветствуется. Подробнее смотрите CONTRIBUTING.md

Алгоритмы

РецептКрейтыКатегории
Генерация случайных чиселrand-badgecat-science-badge
Генерация случайных чисел из диапазонаrand-badgecat-science-badge
Генерация случайных числе с заданным распределениемrand-badge [![rand_distr-badge]][rand_distr]cat-science-badge
Генерация случайных чисел пользовательского типаrand-badgecat-science-badge
Создание случайных паролей из заданного множества символовrand-badgecat-os-badge
Создание случайных паролей из заданного множества символовrand-badgecat-os-badge
Сортировка вектора целых чиселstd-badgecat-science-badge
Сортировка вектора вещественных чиселstd-badgecat-science-badge
Сортировка вектора структурstd-badgecat-science-badge

Командная строка

РецептКрейтыКатегории
Разбор аргументов командной строкиclap-badgecat-command-line-badge
ANSI терминалansi_term-badgecat-command-line-badge

Сжатие данных

РецептКрейтыКатегории
Распаковка tar-архиваflate2-badge tar-badgecat-compression-badge
Запаковка каталога в tar-архивflate2-badge tar-badgecat-compression-badge
Распаковка tar-архива с удалением префиксов путейflate2-badge tar-badgecat-compression-badge

Конкурентность

РецептКрейтыКатегории
Запуск короткоживущего потокаcrossbeam-badgecat-concurrency-badge
Передача данных между двумя потокамиcrossbeam-badgecat-concurrency-badge
Поддержание изменяемого глобального состоянияlazy_static-badgecat-rust-patterns-badge
Параллельное вычисление SHA1 над множеством iso-файловthreadpool-badge walkdir-badge num_cpus-badge ring-badgecat-concurrency-badgecat-filesystem-badge
Рисование фрактала с использованием пула потоковthreadpool-badge num-badge num_cpus-badge image-badgecat-concurrency-badgecat-science-badgecat-rendering-badge
Параллельное изменение элементов массиваrayon-badgecat-concurrency-badge
Параллельная проверка элементов коллекции на соответствие предикатуrayon-badgecat-concurrency-badge
Параллельный поиск элементов, соответствующих предикатуrayon-badgecat-concurrency-badge
Параллельная сортировка вектораrayon-badge rand-badgecat-concurrency-badge
Параллельный map-reducerayon-badgecat-concurrency-badge
Параллельная генерация миниатюр для картинок в формате jpgrayon-badge glob-badge image-badgecat-concurrency-badgecat-filesystem-badge

Криптография

РецептКрейтыКатегории
Вычисление SHA-256 хеша для файлаring-badge data-encoding-badgecat-cryptography-badge
Подписание и проверка сообщения с помощью HMAC хешаring-badgecat-cryptography-badge
Соление и хеширование пароля с PBKDF2ring-badge data-encoding-badgecat-cryptography-badge

Структуры данных

РецептКрейтыКатегории
Определение и работа с типами, представленными в виде битовых полейbitflags-badgecat-no-std-badge

Базы данных

РецептКрейтыКатегории
Создание SQLite базы данныхrusqlite-badgecat-database-badge
Выборка и вставка данныхrusqlite-badgecat-database-badge
Создание таблиц в Postgrespostgres-badgecat-database-badge
Выборка и вставка данныхpostgres-badgecat-database-badge
Агрегирование данныхpostgres-badgecat-database-badge

Дата и время

РецептКрейтыКатегории
Измерение прошедшего времениstd-badgecat-time-badge
Проверяемое вычисление даты и времениchrono-badgecat-date-and-time-badge
Преобразование локального времени во время в другом часовом поясеchrono-badgecat-date-and-time-badge
Просмотр даты и времениchrono-badgecat-date-and-time-badge
Преобразование даты в UNIX-timestamp и обратноchrono-badgecat-date-and-time-badge
Форматированный вывод даты и времениchrono-badgecat-date-and-time-badge
Разбор строки в структуру DateTimechrono-badgecat-date-and-time-badge

Средства разработки

Отладка

Версионирование

РецептКрейтыКатегории
Разбор и увеличение номера версииsemver-badgecat-config-badge
Разбор сложной строки с версиейsemver-badgecat-config-badge
Проверка если версия является предварительным релизомsemver-badgecat-config-badge
Нахождение последней версии в заданном диапазонеsemver-badgecat-config-badge
Проверка версии внешней команды для совместимостиsemver-badgecat-text-processing-badge cat-os-badge

Время сборки

РецептКрейтыКатегории
Компиляция и статическая линковка с библиотекой на языке Ccc-badgecat-development-tools-badge
Компиляция и статическая линковка с библиотекой на языке C++cc-badgecat-development-tools-badge
Компиляция библиотеки на языке C с особыми определениями препроцессораcc-badgecat-development-tools-badge

Кодирование

РецептКрейтыКатегории
%-кодирование строки![percent-encoding-badge]cat-encoding-badge
Кодирование строки как application/x-www-form-urlencodedurl-badgecat-encoding-badge
Кодирование и декодирование шестнадцатеричного представленияdata-encoding-badgecat-encoding-badge
Кодирование и декодирования представления в base64base64-badgecat-encoding-badge
Чтение записей из CSVcsv-badgecat-encoding-badge
Чтение записей из CSV с другим разделителемcsv-badgecat-encoding-badge
Фильтрование записей из CSV, удовлетворяющему предикатуcsv-badgecat-encoding-badge
Обработка неправильных данных в CSV с помощью Serdecsv-badge serde-badgecat-encoding-badge
Сериализация записей в CSVcsv-badgecat-encoding-badge
Сериализация записей в CSV с помощью Serdecsv-badge serde-badgecat-encoding-badge
Преобразование столбца в CSV-файлеcsv-badge serde-badgecat-encoding-badge
Сериализация и десериализация неструктурированных данных в формате JSONserde-json-badgecat-encoding-badge
Десериализация TOML-файлаtoml-badgecat-encoding-badge
Чтение и запись целых числах в прямом порядке байтов (little-endian)byteorder-badgecat-encoding-badge

Файловая система

РецептКрейтыКатегории
Чтение файла построчноstd-badgecat-filesystem-badge
Избегание записи и чтения из одного и того же файлаsame_file-badgecat-filesystem-badge
Произвольный доступ к файлу с использованием проекции в память (memory map)memmap-badgecat-filesystem-badge
Получение файлов, которые были модифицированы за последние 24 часаstd-badgecat-filesystem-badge cat-os-badge
Нахождение циклов в заданном путиsame_file-badgecat-filesystem-badge
Рекурсивное нахождение одинаковых имён файловwalkdir-badgecat-filesystem-badge
Рекурсивное нахождение всех файлов, удовлетворяющим заданному предикатуwalkdir-badgecat-filesystem-badge
Обход каталога с пропуском файлов, начинающихся с точки (dotfiles)walkdir-badgecat-filesystem-badge
Рекурсивное вычисление размеров файлов по заданному путиwalkdir-badgecat-filesystem-badge
Рекурсивное нахождение всех png-файловglob-badgecat-filesystem-badge
Нахождение всех файлов по заданному паттерну без учёта регистра символовglob-badgecat-filesystem-badge

Аппаратный доступ

РецептКрейтыКатегории
Проверка количества ядер процессораПроверка количества ядер процессораcat-hardware-support-badge

Управление памятью

РецептКрейтыКатегории
Определение лениво вычисляемой константыlazy_static-badgecat-caching-badge cat-rust-patterns-badge

Работа с сетью

РецептКрейтыКатегории
Прослушивание неиспользуемого TCP/IP портаstd-badgecat-net-badge

Операционная система

РецептКрейтыКатегории
Запуск внешней команды и обработка её выводаregex-badgecat-os-badge cat-text-processing-badge
Запуск внешней команды, передача ей стандартного ввода и проверка кода ошибкиregex-badgecat-os-badge cat-text-processing-badge
Запуск конвейера внешних командstd-badgecat-os-badge
Перенаправление вывода stdout и stderr дочернего процесса в один и тот же файлstd-badgecat-os-badge
Непрерывная обработка вывода нескольких дочерних процессовstd-badgecat-os-badgecat-text-processing-badge

Наука

Математика

РецептКрейтыКатегории
Сумма вектораndarray-badgecat-science-badge
Норма вектораndarray-badgecat-science-badge
Сложение матрицndarray-badgecat-science-badge
Перемножение матрицndarray-badgecat-science-badge
Умножение скаляра на вектор и матрицуndarray-badgecat-science-badge
Инвертирование матрицыnalgebra-badgecat-science-badge
Расчёт боковой стороны треугольникаstd-badgecat-science-badge
Проверка, что тангенс равен синусу делённому на косинусstd-badgecat-science-badge
Расстояние между двумя точками на поверхности сферыstd-badgecat-science-badge
Создание комплексных чиселnum-badgecat-science-badge
Сложение комплексных числеnum-badgecat-science-badge
Математические функции комплексных чиселnum-badgecat-science-badge
Измерение среднего значенияstd-badgecat-science-badge
Вычисление стандартного отклоненияstd-badgecat-science-badge
Большие целые числаnum-badgecat-science-badge

Обработка текста

РецептКрейтыКатегории
Получение графем Unicodeunicode-segmentation-badgecat-encoding-badge
Проверка и извлечение логина из email-адресаregex-badge lazy_static-badgecat-text-processing-badge
Извлечения списка из текста уникальных #тэговregex-badge lazy_static-badgecat-text-processing-badge
Извлечение из текста номеров телефоновregex-badgecat-text-processing-badge
Фильтрация лог-файла с помощью нескольких регулярных выраженийregex-badgecat-text-processing-badge
Замена всех вхождений одного текстового шаблона на другойregex-badge lazy_static-badgecat-text-processing-badge
Реализация типажа FromStr для определённой пользователем structstd-badgecat-text-processing-badge

Веб-программирование

Обработка веб-страниц

РецептКрейтыКатегории
Извлечение всех ссылок из HTML-страницыreqwest-badge select-badgecat-net-badge
Проверка веб-страницы на наличие мёртвых ссылокreqwest-badge select-badge url-badgecat-net-badge
Извлечение всех уникальных ссылок из страницы в формате MediaWikireqwest-badge regex-badgecat-net-badge

Работа с унифицированными указателями ресурсов (URL)

РецептКрейтыКатегории
Парсинг URL из строки в значение типа Urlurl-badgecat-net-badge
Создание базового URL удалением сегментов путиurl-badgecat-net-badge
Создание новых URL из базового URLurl-badgecat-net-badge
Извлечение URL источника (тройки схема / хост / порт)url-badgecat-net-badge
Удаление параметров запроса из URLurl-badgecat-net-badge

Типы мультимедиа (MIME)

РецептКрейтыКатегории
Получение типа MIME из строкиmime-badgecat-encoding-badge
Получение типа MIME из имени файлаmime-badgecat-encoding-badge
Парсинг типа MIME из HTTP ответаmime-badge reqwest-badgecat-net-badge cat-encoding-badge

Клиенты

Немного о том, как "готовить" Rust

Содержание

Для кого эта книга

Эта книга предназначена для новичков в Rust, таким образом они могут быстро получить обзор возможностей экосистемы выросшей вокруг Rust. Книга также предназначена и для опытных разработчиков на Rust, которым иногда требуется взглянуть в рецепт, чтобы быстро вспомнить, как сделать какую-то обычную задачу, возникшую на практике.

Как читать эту книгу

Книга содержит указатель всех рецептов, организованных в разделы: "основы", "кодирование данных", "конкурентность" и так далее. Сами по себе разделы более или менее упорядочены по сложности, более поздние разделы обычно требуют подготовки, и иногда требуют понимания материала, встречающегося в предыдущих разделах.

В указателе каждый раздел содержит список рецептов. Рецепты представляют собой постановку задачи, например "сгенерировать случайные числа из диапазона"; и каждый рецепт помечен "шильдиками", показывающими какие крейты они используют (крейты (crates) - так называются библиотеки в экосистеме Rust), например rand-badge, а также к каким категориям в crates.io эти крейты принадлежат, например cat-science-badge.

Новичкам скорее всего будет комфортно читать последовательно с первого раздела до последнего и такое чтение даст хорошее представление об экосистеме. Нажимайте на заголовок раздела в указателе или на боковой панели, чтобы перейти прямо к нужному разделу в книге.

Если вы просто ищете решение для какой-то простой задачи, эта книга на данный момент более трудна для такого поиска. Самый простой путь чтобы найти нужный рецепт - это просмотреть указатель на предмет нужных крейтов и категорий, которые вас интересуют. И там уже нажимайте на название рецепта, чтобы посмотреть его полностью. Эту неэффективность мы исправим в будущем.

Как использовать рецепты

Рецепты построены так, чтобы дать вам мгновенный доступ к работающему коду наряду с полным объяснением, как он работает и дать вам пути, для получения дальнейшей информации по этой теме.

Все рецепты в этом сборнике являются полными, самодостаточными программами и могут быть скопированы прямо в ваш проект для дальнейших экспериментов с ними. Чтобы сделать это следуйте инструкциям ниже.

Рассмотрим пример "сгенерировать случайные числа из диапазона":

rand-badge cat-science-badge

extern crate rand;
use rand::Rng;
fn main() {
    let mut rng = rand::thread_rng();
    println!("Random f64: {}", rng.gen::<f64>());
}

Чтобы "поиграться" с ним локально, вам нужно запустить следующие команды для создания нового cargo-проекта (cargo - это пакетный менеджер для Rust) и перейти внутрь только что созданного проекта:

cargo new my-example --bin
cd my-example

Теперь вам также нужно добавить все необходимые крейты в файл Cargo.toml, как показано в "шильдиках", в данном случае просто rand. Чтобы это сделать, используйте команду cargo add, которая реализована в крейте cargo-edit и вам его нужно сначала установить:

cargo install cargo-edit
cargo add rand

Теперь вы можете заменить содержимое файла src/main.rs на полностью скопированное из рецепта и запустить этот код:

cargo run

Шильдики крейтов, которые сопровождают примеры кода также содержат ссылки на документацию по соответствующим крестам на сайте docs.rs, и часто на дополнительную документацию по тем крестам, которые могут быть вам полезны и возможно подходят вам для решения вашей задачи.

Несколько слов об обработке ошибок

Обработка ошибок в Rust надёжна, если её использовать правильно, но в настоящее время требует некоторого количества повторяющегося кода. Из-за такого повторяющегося кода (boilerplate) можно встретить примеры программ на Rust, которые содержат вызовы unwrap вместо правильной обработки ошибок.

Поскольку эти рецепты предназначены для использования в реальных проектах и должны внедрить наилучшие практики, они также сделаны таким образом, чтобы обеспечить правильную обработку ошибок в случаях, когда появляются типы Option и Result.

Основной шаблон - это использовать главную функцию fn main() -> Result.

Обычно структура примеров выглядит как-то так:

#[macro_use]
extern crate error_chain;
use std::net::IpAddr;
use std::str;
error_chain! {
    foreign_links {
        Utf8(std::str::Utf8Error);
        AddrParse(std::net::AddrParseError);
    }
}
fn main() -> Result<()> {
    let bytes = b"2001:db8::1";
    // Bytes to string.
    let s = str::from_utf8(bytes)?;
    // String to IP address.
    let addr: IpAddr = s.parse()?;
    println!("{:?}", addr);
    Ok(())
}

Этот пример использует макрос error_chain! чтобы определить пользовательские типы Error и Result, наряду с автоматическими преобразованиями для типов ошибок из стандартной библиотеки. Такие автоматические преобразования позволят работать оператору ?.

Для целей повышения читаемости механический код связанный с обработкой ошибок по умолчанию скрыт. Чтобы посмотреть полное содержимое примера, нажмите на кнопку "expand" (), помещённую в верхнем левом углу фрагмента кода.

#[macro_use]
extern crate error_chain;
extern crate url;
use url::{Url, Position};

error_chain! {
    foreign_links {
        UrlParse(url::ParseError);
    }
}
fn main() -> Result<()> {
    let parsed = Url::parse("https://httpbin.org/cookies/set?k2=v2&k1=v1")?;
    let cleaned: &str = &parsed[..Position::AfterPath];
    println!("cleaned: {}", cleaned);
    Ok(())
}

Для более глубокого изучения обработки ошибок в Rust, читайте эту главу в Rust book, этот блог пост, (и документацию по крейту https://docs.rs/snafu/0.1.4/snafu/ - прим. перев.)

Несколько слов о выборе крейтов

Эта книга предназначена в конечном итоге дать полноценное покрытие экосистемы вокруг Rust, но на сегодняшний день книга ограничена в охвате, пока мы работаем над инструментами и способом подачи материала книги. Надеемся, начав с малого охвата и постепенно расширяясь книга скоро станет источником высокого качества и будет поддерживать высокий стандарт качества по мере наполнения.

В настоящий момент книга Rust. Сборник рецептов в-основном сосредоточена на стандартной библиотеке, и на "центральных" или "основных" крейтах - эти крейты обеспечивают выполнение самых распространённых программистских задач, и остальная часть экосистемы выстроена на этой основе.

Эта книга очень близко связана с Rust Libz Blitz, проектом, чтобы собрать информацию о библиотеках и улучшить их качество и книга оставляет "привилегию" выбора за этим проектом. Любой крейт, упомянутый в книге уже прошёл проверку в рамках этого проекта, но также есть и крейты ожидающие такой проверки.

Алгоритмы

РецептКрейтыКатегории
Генерация случайных чиселrand-badgecat-science-badge
Генерация случайных чисел из диапазонаrand-badgecat-science-badge
Генерация случайных числе с заданным распределениемrand-badge [![rand_distr-badge]][rand_distr]cat-science-badge
Генерация случайных чисел пользовательского типаrand-badgecat-science-badge
Создание случайных паролей из заданного множества символовrand-badgecat-os-badge
Создание случайных паролей из заданного множества символовrand-badgecat-os-badge
Сортировка вектора целых чиселstd-badgecat-science-badge
Сортировка вектора вещественных чиселstd-badgecat-science-badge
Сортировка вектора структурstd-badgecat-science-badge

Генерация случайных значений

Генерация случайных чисел

rand-badge cat-science-badge

Пример генерирует случайные числа с помощью генератора (псевдо)случайных чисел rand::Rng полученного через rand::thread_rng. Каждый поток операционной системы имеет свой инициализированный генератор. Сгенерированные целые числа распределены равномерно на диапазоне, заданного их типом, а вещественные числа генерируются из диапазона от 0 до 1, не включая само число 1.

extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();

    let n1: u8 = rng.gen();
    let n2: u16 = rng.gen();
    println!("Random u8: {}", n1);
    println!("Random u16: {}", n2);
    println!("Random u32: {}", rng.gen::<u32>());
    println!("Random i32: {}", rng.gen::<i32>());
    println!("Random float: {}", rng.gen::<f64>());
}

Генерация случайных чисел из диапазона

rand-badge cat-science-badge

Пример генерирует случайные числа из полуоткрытого диапазона [0, 10) (не включающего 10) с помощью метода Rng::gen_range.

extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    println!("Integer: {}", rng.gen_range(0, 10));
    println!("Float: {}", rng.gen_range(0.0, 10.0));
}

Uniform задаёт равномерное распределение. Его использование работает точно так же, но может работать быстрее, когда нам нужно генерировать числа из данного диапазона много раз.

extern crate rand;


use rand::distributions::{Distribution, Uniform};

fn main() {
    let mut rng = rand::thread_rng();
    let die = Uniform::from(1..7);

    loop {
        let throw = die.sample(&mut rng);
        println!("Roll the die: {}", throw);
        if throw == 6 {
            break;
        }
    }
}

Генерация случайных числе с заданным распределением

[![rand_distr-badge]][rand_distr] cat-science-badge

По умолчанию, случайные числа из крейта rand имеют непрерывное равномерное распределение. Крейт rand_distr предоставляет другие типы распределений. Для их использования вы создаёте экземпляр распределения, затем элемент из этого распределения используя метод Distribution::sample с помощью генератора случайных чисел rand::Rng.

Доступные распределения описаны здесь. Пример использования нормального распределения Normal показан ниже.

use rand_distr::{Distribution, Normal, NormalError};
use rand::thread_rng;

fn main() -> Result<(), NormalError> {
    let mut rng = thread_rng();
    let normal = Normal::new(2.0, 3.0)?;
    let v = normal.sample(&mut rng);
    println!("{} is from a N(2, 9) distribution", v);
    Ok(())
}

Генерация случайных чисел пользовательского типа

rand-badge cat-science-badge

Пример случайно генерирует пару (i32, bool, f64) и переменную пользовательского типа Point. Реализует типаж Distribution от Point для особенного типа Standard для того, чтобы включить возможность случайной генерации значений Point.

extern crate rand;

use rand::Rng;
use rand::distributions::{Distribution, Standard};

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl Distribution<Point> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
        let (rand_x, rand_y) = rng.gen();
        Point {
            x: rand_x,
            y: rand_y,
        }
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let rand_tuple = rng.gen::<(i32, bool, f64)>();
    let rand_point: Point = rng.gen();
    println!("Random tuple: {:?}", rand_tuple);
    println!("Random Point: {:?}", rand_point);
}

Создание случайных паролей из букв и цифр

rand-badge cat-os-badge

Пример случайно генерирует строку с заданной длиной из ASCII символов из диапазона A-Z, a-z, 0-9 с помощью образца Alphanumeric.

extern crate rand;

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .collect();

    println!("{}", rand_string);
}

Создание случайных паролей из заданного множества символов

rand-badge cat-os-badge

Пример случайно генерирует строку заданной длины из ASCII символов по заданному пользователем байтовой строки с помощью gen_range.

fn main() {
    use rand::Rng;
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                            abcdefghijklmnopqrstuvwxyz\
                            0123456789)(*&^%$#@!~";
    const PASSWORD_LEN: usize = 30;
    let mut rng = rand::thread_rng();

    let password: String = (0..PASSWORD_LEN)
        .map(|_| {
            let idx = rng.gen_range(0, CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();

    println!("{:?}", password);
}

Сортировка вектора

Сортировка вектора целых чисел

std-badge cat-science-badge

Этот пример сортирует вектор целых чисел с помощью метода vec::sort. Альтернативой было бы использовать vec::sort_unstable, который может быть быстрее в некоторых случаях, но не гарантирует сохранение порядка равных элементов.

fn main() {
    let mut vec = vec![1, 5, 10, 2, 15];
    
    vec.sort();

    assert_eq!(vec, vec![1, 2, 5, 10, 15]);
}

Сортировка вектора вещественных чисел

std-badge cat-science-badge

Вектор из f32 or f64 может быть отсортирован с помощью методов vec::sort_by и PartialOrd::partial_cmp.

fn main() {
    let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];

    vec.sort_by(|a, b| a.partial_cmp(b).unwrap());

    assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]);
}

Сортировка вектора структур

std-badge cat-science-badge

Пример сортирует вектор персон (то есть структур типа Person) с полями name и age согласно естественному порядку (по имени и возрасту). Чтобы сделать структуру Person сортируемой, нам нужно реализовать четыре типажа: Eq, PartialEq, Ord and PartialOrd. Эти типажи могут быть просто реализованы автоматически с помощью аннотации derive. Мы можем также передать нашу функцию сравнения используя метод vec:sort_by и отсортировать только по возрасту.

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Person {
    name: String,
    age: u32
}

impl Person {
    pub fn new(name: String, age: u32) -> Self {
        Person {
            name,
            age
        }
    }
}

fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("John".to_string(), 1),
    ];

    // Sort people by derived natural order (Name and age)
    people.sort();

    assert_eq!(
        people,
        vec![
            Person::new("Al".to_string(), 60),
            Person::new("John".to_string(), 1),
            Person::new("Zoe".to_string(), 25),
        ]);

    // Sort people by age
    people.sort_by(|a, b| b.age.cmp(&a.age));

    assert_eq!(
        people,
        vec![
            Person::new("Al".to_string(), 60),
            Person::new("Zoe".to_string(), 25),
            Person::new("John".to_string(), 1),
        ]);

}

Командная строка

РецептКрейтыКатегории
Разбор аргументов командной строкиclap-badgecat-command-line-badge
ANSI терминалansi_term-badgecat-command-line-badge

Основы Clap

Разбор аргументов командной строки

clap-badge cat-command-line-badge

Это приложение описывает структуру своего интерфейса командной строки используя стиль цепочек вызовов из clap. Документация даёт два других возможных способа для построения приложения.

В стиле цепочек вызовов (builder style), with_name это уникальный идентификатор, который использует value_of для получения переданного значения. Опции short и long контролируют флаг, который пользователь будет использовать и печатать в командной строке; короткие флаги выглядят как -f, а длинные флаги выглядят как --file.

extern crate clap;

use clap::{Arg, App};

fn main() {
    let matches = App::new("My Test Program")
        .version("0.1.0")
        .author("Hackerman Jones <hckrmnjones@hack.gov>")
        .about("Teaches argument parsing")
        .arg(Arg::with_name("file")
                 .short("f")
                 .long("file")
                 .takes_value(true)
                 .help("A cool file"))
        .arg(Arg::with_name("num")
                 .short("n")
                 .long("number")
                 .takes_value(true)
                 .help("Five less than your favorite number"))
        .get_matches();

    let myfile = matches.value_of("file").unwrap_or("input.txt");
    println!("The file passed is: {}", myfile);

    let num_str = matches.value_of("num");
    match num_str {
        None => println!("No idea what your favorite number is."),
        Some(s) => {
            match s.parse::<i32>() {
                Ok(n) => println!("Your favorite number must be {}.", n + 5),
                Err(_) => println!("That's not a number! {}", s),
            }
        }
    }
}

Документация по интерфейсу командной строки генерируется непосредственно библиотекой clap. Для данного приложения вывод выглядит примерно следующим образом.

My Test Program 0.1.0
Hackerman Jones <hckrmnjones@hack.gov>
Teaches argument parsing

USAGE:
    testing [OPTIONS]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -f, --file <file>     A cool file
    -n, --number <num>    Five less than your favorite number

Мы можем протестировать приложение запустив команду следующим образом.

$ cargo run -- -f myfile.txt -n 251

Вывод команды:

The file passed is: myfile.txt
Your favorite number must be 256.

ANSI терминал

ANSI терминал

ansi_term-badge cat-command-line-badge

Эта программа обрисовывает использование ansi_term крейта и как он используется для отображения текста с различным цветом, форматированием, к примеру, жирный синий или жёлтый подчёркнутый текст, на ANSI терминалах.

У нас в наличии две главных структуры данных в [ansi_term]: ANSIString и Style. Структура Style содержит информацию о стиле: цвета, должен ли текст быть жирным или мигающим или ещё каким-то. Есть два варианта Colour, которые представляют простые цвета букв. Структура ANSIString -- это просто строка в паре со Style.

Примечание: Если вы изучаете английский язык (а это скорее всего вещь обязательная для сферы IT), то стоит заметить, что в британском английском используется слово Colour, а в американском английском Color. При этом в библиотеке определён именно тип Colour, не путайте.

Вывод цветного текста на терминал

extern crate ansi_term;

use ansi_term::Colour;

fn main() {
    println!("This is {} in color, {} in color and {} in color",
             Colour::Red.paint("red"),
             Colour::Blue.paint("blue"),
             Colour::Green.paint("green"));
}

Жирный текст в терминале

Для чего-то более сложного, чем простое изменения цвета переднего плана, нам уже нужно сконструировать экземпляр Style. Метод Style::new() создаёт экземпляр и её свойства устанавливаются цепочкой вызовов.

extern crate ansi_term;

use ansi_term::Style;

fn main() {
    println!("{} and this is not",
             Style::new().bold().paint("This is Bold"));
}

Жирный и цветной текст в терминале

Colour реализует множество функций, наподобие функциям из Style и позволяет объединять вызовы в цепочку.

extern crate ansi_term;

use ansi_term::Colour;
use ansi_term::Style;

fn main(){
    println!("{}, {} and {}",
             Colour::Yellow.paint("This is colored"),
             Style::new().bold().paint("this is bold"),
             Colour::Yellow.bold().paint("this is bold and colored"));
}

Сжатие данных

РецептКрейтыКатегории
Распаковка tar-архиваflate2-badge tar-badgecat-compression-badge
Запаковка каталога в tar-архивflate2-badge tar-badgecat-compression-badge
Распаковка tar-архива с удалением префиксов путейflate2-badge tar-badgecat-compression-badge

Работа с tar-архивами

Распаковка tar-архива

flate2-badge tar-badge cat-compression-badge

Пример разжимает с помощью (GzDecoder) и распаковывает с помощью (Archive::unpack) все файлы из сжатого tar-архива archive.tar.gz в текущем рабочем каталоге. Распаковка осуществляется в том же самом каталоге.

extern crate flate2;
extern crate tar;

use std::fs::File;
use flate2::read::GzDecoder;
use tar::Archive;

fn main() -> Result<(), std::io::Error> {
    let path = "archive.tar.gz";

    let tar_gz = File::open(path)?;
    let tar = GzDecoder::new(tar_gz);
    let mut archive = Archive::new(tar);
    archive.unpack(".")?;

    Ok(())
}

Запаковка каталога в tar-архив

flate2-badge tar-badge cat-compression-badge

Сжатие каталога /var/log directory в archive.tar.gz.

Этот пример создаёт File, обёрнутый в GzEncoder и tar::Builder. Рекурсивно добавляет содержимое каталога /var/log в архив по пути backup/logs с помощью Builder::append_dir_all. GzEncoder ответственен за прозрачный интерфейс для сжатия данных до записи их в файл archive.tar.gz.

extern crate tar;
extern crate flate2;

use std::fs::File;
use flate2::Compression;
use flate2::write::GzEncoder;

fn main() -> Result<(), std::io::Error> {
    let tar_gz = File::create("archive.tar.gz")?;
    let enc = GzEncoder::new(tar_gz, Compression::default());
    let mut tar = tar::Builder::new(enc);
    tar.append_dir_all("backup/logs", "/var/log")?;
    Ok(())
}

Распаковка tar-архива с удалением префиксов путей

flate2-badge tar-badge cat-compression-badge

Код в примере итерируется с помощью Archive::entries. Мы используем Path::strip_prefix, чтобы удалить заданный префикс (bundle/logs). Наконец, распаковываем каждый tar::Entry с помощью Entry::unpack.

#[macro_use]
extern crate error_chain;
extern crate flate2;
extern crate tar;

use std::fs::File;
use std::path::PathBuf;
use flate2::read::GzDecoder;
use tar::Archive;

error_chain! {
  foreign_links {
    Io(std::io::Error);
    StripPrefixError(::std::path::StripPrefixError);
  }
}

fn main() -> Result<()> {
    let file = File::open("archive.tar.gz")?;
    let mut archive = Archive::new(GzDecoder::new(file));
    let prefix = "bundle/logs";

    println!("Extracted the following files:");
    archive
        .entries()?
        .filter_map(|e| e.ok())
        .map(|mut entry| -> Result<PathBuf> {
            let path = entry.path()?.strip_prefix(prefix)?.to_owned();
            entry.unpack(&path)?;
            Ok(path)
        })
        .filter_map(|e| e.ok())
        .for_each(|x| println!("> {}", x.display()));

    Ok(())
}

Конкурентность

РецептКрейтыКатегории
Запуск короткоживущего потокаcrossbeam-badgecat-concurrency-badge
Передача данных между двумя потокамиcrossbeam-badgecat-concurrency-badge
Поддержание изменяемого глобального состоянияlazy_static-badgecat-rust-patterns-badge
Параллельное вычисление SHA1 над множеством iso-файловthreadpool-badge walkdir-badge num_cpus-badge ring-badgecat-concurrency-badgecat-filesystem-badge
Рисование фрактала с использованием пула потоковthreadpool-badge num-badge num_cpus-badge image-badgecat-concurrency-badgecat-science-badgecat-rendering-badge
Параллельное изменение элементов массиваrayon-badgecat-concurrency-badge
Параллельная проверка элементов коллекции на соответствие предикатуrayon-badgecat-concurrency-badge
Параллельный поиск элементов, соответствующих предикатуrayon-badgecat-concurrency-badge
Параллельная сортировка вектораrayon-badge rand-badgecat-concurrency-badge
Параллельный map-reducerayon-badgecat-concurrency-badge
Параллельная генерация миниатюр для картинок в формате jpgrayon-badge glob-badge image-badgecat-concurrency-badgecat-filesystem-badge

Потоки

Запуск короткоживущего потока

crossbeam-badge cat-concurrency-badge

Этот пример использует крейт crossbeam, который содержит структуры данных и функции для параллельного и многопоточного программирования. Scope::spawn порождает новый поток со своей областью видимости, который гарантированно прекращается после возврата из области замыкания, переданной в функцию crossbeam::scope. Это означает, что можем безопасно ссылаться на данные из вызванной функции.

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

extern crate crossbeam;

fn main() {
    let arr = &[1, 25, -4, 10];
    let max = find_max(arr);
    assert_eq!(max, Some(25));
}

fn find_max(arr: &[i32]) -> Option<i32> {
    const THRESHOLD: usize = 2;
  
    if arr.len() <= THRESHOLD {
        return arr.iter().cloned().max();
    }

    let mid = arr.len() / 2;
    let (left, right) = arr.split_at(mid);
  
    crossbeam::scope(|s| {
        let thread_l = s.spawn(|_| find_max(left));
        let thread_r = s.spawn(|_| find_max(right));
  
        let max_l = thread_l.join().unwrap()?;
        let max_r = thread_r.join().unwrap()?;
  
        Some(max_l.max(max_r))
    }).unwrap()
}

Передача данных между двумя потоками

crossbeam-badge cat-concurrency-badge

Этот пример демонстрирует использование crossbeam-channel с одиночным производителем и одиночным потребителем (single producer - single consumer, SPSC). Мы далее развиваем ex-crossbeam-spawn пример, используя crossbeam::scope и Scope::spawn для управления производящим потоком. Данные передаются между двумя потоками через crossbeam_channel::unbounded канал, то есть в канале может храниться неограниченное количество сообщений (которое фактически ограничено доступным объёмом памяти). Поток-производитель засыпает на полсекунды между посылкой сообщений.

extern crate crossbeam;
extern crate crossbeam_channel;

use std::{thread, time};
use crossbeam_channel::unbounded;

fn main() {
    let (snd, rcv) = unbounded();
    let n_msgs = 5;
    crossbeam::scope(|s| {
        s.spawn(|_| {
            for i in 0..n_msgs {
                snd.send(i).unwrap();
                thread::sleep(time::Duration::from_millis(100));
            }
        });
    }).unwrap();
    for _ in 0..n_msgs {
        let msg = rcv.recv().unwrap();
        println!("Received {}", msg);
    }
}

Поддержание изменяемого глобального состояния

lazy_static-badge cat-rust-patterns-badge

В примере объявляется глобальное состояние используя lazy_static. lazy_static создаёт объект, доступный глобально через ссылку static ref, которая требует Mutex, чтобы сделать этот объект изменяемым (можно также использовать RwLock). Обёртка Mutex гарантирует, что к этому состоянию не может быть одновременно получен доступ из множества потоков, таким образом предотвращая гонки. Чтобы прочитать или записать в объект, защищённым Mutex-ом, нужно получить специальный объект типа MutexGuard, и осуществлять доступ через него.

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;

use std::sync::Mutex;

error_chain!{ }

lazy_static! {
    static ref FRUIT: Mutex<Vec<String>> = Mutex::new(Vec::new());
}

fn insert(fruit: &str) -> Result<()> {
    let mut db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?;
    db.push(fruit.to_string());
    Ok(())
}

fn main() -> Result<()> {
    insert("apple")?;
    insert("orange")?;
    insert("peach")?;
    {
        let db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?;

        db.iter().enumerate().for_each(|(i, item)| println!("{}: {}", i, item));
    }
    insert("grape")?;
    Ok(())
}

Конкурентный расчёт хеш-суммы SHA256 iso файлов

threadpool-badge num_cpus-badge walkdir-badge ring-badge cat-concurrency-badgecat-filesystem-badge

Этот пример рассчитывает SHA256 для всех файлов с расширением iso в текущей директории. Пул потоков генерирует потоки в количестве, равному количеству ядер в системе найденные с помощью num_cpus::get. Walkdir::new итерируется по директории и вызывает execute чтобы совершить операцию чтения файла и вычисления SHA1 хэш-суммы.

extern crate walkdir;
extern crate ring;
extern crate num_cpus;
extern crate threadpool;

use walkdir::WalkDir;
use std::fs::File;
use std::io::{BufReader, Read, Error};
use std::path::Path;
use threadpool::ThreadPool;
use std::sync::mpsc::channel;
use ring::digest::{Context, Digest, SHA256};

// Проверка iso расширения
fn is_iso(entry: &Path) -> bool {
    match entry.extension() {
        Some(e) if e.to_string_lossy().to_lowercase() == "iso" => true,
        _ => false,
    }
}

fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> {
    let mut buf_reader = BufReader::new(File::open(&filepath)?);
    let mut context = Context::new(&SHA256);
    let mut buffer = [0; 1024];

    loop {
        let count = buf_reader.read(&mut buffer)?;
        if count == 0 {
            break;
        }
        context.update(&buffer[..count]);
    }

    Ok((context.finish(), filepath))
}

fn main() -> Result<(), Error> {
    let pool = ThreadPool::new(num_cpus::get());

    let (tx, rx) = channel();

    for entry in WalkDir::new("/home/user/Downloads")
        .follow_links(true)
        .into_iter()
        .filter_map(|e| e.ok())
        .filter(|e| !e.path().is_dir() && is_iso(e.path())) {
            let path = entry.path().to_owned();
            let tx = tx.clone();
            pool.execute(move || {
                let digest = compute_digest(path);
                tx.send(digest).expect("Could not send data!");
            });
        }

    drop(tx);
    for t in rx.iter() {
        let (sha, path) = t?;
        println!("{:?} {:?}", sha, path);
    }
    Ok(())
}

{{#include thread/threadpool-fractal.md}}

Параллелизм

Параллельное изменение элементов массива

rayon-badge cat-concurrency-badge

Этот пример использует крейт rayon, библиотеку с примитивами для параллелизма данных в Rust. rayon реализует метод par_iter_mut для любого параллельного Iterable типа данных. Это особый тип итератора, который может быть потенциально выполняться параллельно.

extern crate rayon;

use rayon::prelude::*;

fn main() {
    let mut arr = [0, 7, 9, 11];
    arr.par_iter_mut().for_each(|p| *p -= 1);
    println!("{:?}", arr);
}

Параллельная проверка элементов коллекции на соответствие предикату

rayon-badge cat-concurrency-badge

Этот пример демонстрирует методы rayon::any и rayon::all, которые являются параллельными аналогами std::any и std::all соответственно. Метод rayon::any параллельно проверяет, выполняется ли предикат для какого-нибудь элемента итератора и, если да, то как можно раньше возвращает такой элемент. Метод rayon::all параллельно проверяет, выполняется ли предикат для всех элементов итератора, и, если нет, то как можно раньше возвращает элемент, нарушающий условие предиката.

extern crate rayon;

use rayon::prelude::*;

fn main() {
    let mut vec = vec![2, 4, 6, 8];

    assert!(!vec.par_iter().any(|n| (*n % 2) != 0));
    assert!(vec.par_iter().all(|n| (*n % 2) == 0));
    assert!(!vec.par_iter().any(|n| *n > 8 ));
    assert!(vec.par_iter().all(|n| *n <= 8 ));

    vec.push(9);

    assert!(vec.par_iter().any(|n| (*n % 2) != 0));
    assert!(!vec.par_iter().all(|n| (*n % 2) == 0));
    assert!(vec.par_iter().any(|n| *n > 8 ));
    assert!(!vec.par_iter().all(|n| *n <= 8 )); 
}

Параллельный поиск элементов, соответствующих предикату

rayon-badge cat-concurrency-badge

Этот пример использует rayon::find_any и par_iter для параллельного поиска в векторе, удовлетворяющего предикату, который (предикат) передан как замыкание.

Если имеется несколько элементов, удовлетворяющих предикату, переданному в rayon::find_any, rayon возвращает первый попавшийся такой элемент, и это необязательно будет первый с начала вектора.

Также стоит заметить, что аргумент в замыкании это ссылка на ссылку (&&x). Стоит ознакомиться с документацией к std::find, чтобы понять, почему сделано именно так.

extern crate rayon;

use rayon::prelude::*;

fn main() {
    let v = vec![6, 2, 1, 9, 3, 8, 11];

    let f1 = v.par_iter().find_any(|&&x| x == 9);
    let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6);
    let f3 = v.par_iter().find_any(|&&x| x > 8);

    assert_eq!(f1, Some(&9));
    assert_eq!(f2, Some(&8));
    assert!(f3 > Some(&8));
}

Параллельная сортировка вектора

rayon-badge rand-badge cat-concurrency-badge

Этот пример параллельно сортирует вектор строк.

Более детально, пример создаёт вектор пустых строк, а затем, используя par_iter_mut().for_each, параллельно заполняет этот вектор случайными строками. Несмотря на то, что существует множество способов для сортировки элементов, par_sort_unstable обычно быстрее, чем алгоритмы стабильной сортировки.

extern crate rand;
extern crate rayon;

use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use rayon::prelude::*;

fn main() {
  let mut vec = vec![String::new(); 100_000];
  vec.par_iter_mut().for_each(|p| {
    let mut rng = thread_rng();
    *p = (0..5).map(|_| rng.sample(&Alphanumeric)).collect()
  });
  vec.par_sort_unstable();
}

Параллельный map-reduce

rayon-badge cat-concurrency-badge

Этот пример использует методы rayon::filter, rayon::map и rayon::reduce для вычисления среднего возраста объектов Person, у которых возраст больше 30.

Метод rayon::filter возвращает элементы из коллекции, которые удовлетворяют заданному предикату. Метод rayon::map выполняет заданную операцию над каждым элементом, тем самым создавая новый параллельный итератор. Наконец, метод rayon::reduce выполняет операцию свёртки, то есть некоторую операцию над предыдущей итерацией свёртки и текущим элементом. Пример также демонстрирует использование метода rayon::sum, который даёт тот же самый результат, что и операция свёртки с переданной функцией сложения и нулём в качестве начального значения.

extern crate rayon;

use rayon::prelude::*;

struct Person {
    age: u32,
}

fn main() {
    let v: Vec<Person> = vec![
        Person { age: 23 },
        Person { age: 19 },
        Person { age: 42 },
        Person { age: 17 },
        Person { age: 17 },
        Person { age: 31 },
        Person { age: 30 },
    ];

    let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32;
    let sum_over_30 = v.par_iter()
        .map(|x| x.age)
        .filter(|&x| x > 30)
        .reduce(|| 0, |x, y| x + y);

    let alt_sum_30: u32 = v.par_iter()
        .map(|x| x.age)
        .filter(|&x| x > 30)
        .sum();

    let avg_over_30 = sum_over_30 as f32 / num_over_30;
    let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30;

    assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON);
    println!("The average age of people older than 30 is {}", avg_over_30);
}

{{#include parallel/rayon-thumbnails.md}}

Криптография

РецептКрейтыКатегории
Вычисление SHA-256 хеша для файлаring-badge data-encoding-badgecat-cryptography-badge
Подписание и проверка сообщения с помощью HMAC хешаring-badgecat-cryptography-badge
Соление и хеширование пароля с PBKDF2ring-badge data-encoding-badgecat-cryptography-badge

Хэширование

Вычисление SHA-256 хеша для файла

ring-badge data-encoding-badge cat-cryptography-badge

Код в примере пишет некоторые данные в файл на диске, затем вычисляет SHA-256 digest::Digest из содержимое файла посредством digest::Context.

#[macro_use]
extern crate error_chain;
extern crate data_encoding;
extern crate ring;

use data_encoding::HEXUPPER;
use ring::digest::{Context, Digest, SHA256};
use std::fs::File;
use std::io::{BufReader, Read, Write};

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Decode(data_encoding::DecodeError);
    }
}

fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> {
    let mut context = Context::new(&SHA256);
    let mut buffer = [0; 1024];

    loop {
        let count = reader.read(&mut buffer)?;
        if count == 0 {
            break;
        }
        context.update(&buffer[..count]);
    }

    Ok(context.finish())
}

fn main() -> Result<()> {
    let path = "file.txt";

    let mut output = File::create(path)?;
    write!(output, "We will generate a digest of this text")?;

    let input = File::open(path)?;
    let reader = BufReader::new(input);
    let digest = sha256_digest(reader)?;

    println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref()));

    Ok(())
}

Подпись и проверка сообщения с помощью HMAC хеша

ring-badge cat-cryptography-badge

Пример использует ring::hmac для создания hmac::Signature из строки, а затем проверяет, что сигнатура корректна.

extern crate ring;

use ring::{hmac, rand};
use ring::rand::SecureRandom;
use ring::error::Unspecified;

fn main() -> Result<(), Unspecified> {
    let mut key_value = [0u8; 48];
    let rng = rand::SystemRandom::new();
    rng.fill(&mut key_value)?;
    let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value);

    let message = "Legitimate and important message.";
    let signature = hmac::sign(&key, message.as_bytes());
    hmac::verify(&key, message.as_bytes(), signature.as_ref())?;

    Ok(())
}

Шифрование

Соление и хэширование пароля с PBKDF2

ring-badge data-encoding-badge cat-cryptography-badge

Код в примере использует пакет ring::pbkdf2. Чтобы вычислить хэш-сумму "солёного" пароля, использует функцию вывода ключа PBKDF2 pbkdf2::derive. Далее проверяет корректность хэш-кода с помощью метода pbkdf2::verify. Соль генерируется используя функцию SecureRandom::fill, которая заполняет байтовый массив для соли с помощью криптографического генератора случайных чисел.

extern crate ring;
extern crate data_encoding;

use data_encoding::HEXUPPER;
use ring::error::Unspecified;
use ring::rand::SecureRandom;
use ring::{digest, pbkdf2, rand};
use std::num::NonZeroU32;

fn main() -> Result<(), Unspecified> {
    const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
    let n_iter = NonZeroU32::new(100_000).unwrap();
    let rng = rand::SystemRandom::new();

    let mut salt = [0u8; CREDENTIAL_LEN];
    rng.fill(&mut salt)?;

    let password = "Guess Me If You Can!";
    let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN];
    pbkdf2::derive(
        pbkdf2::PBKDF2_HMAC_SHA512,
        n_iter,
        &salt,
        password.as_bytes(),
        &mut pbkdf2_hash,
    );
    println!("Salt: {}", HEXUPPER.encode(&salt));
    println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash));

    let should_succeed = pbkdf2::verify(
        pbkdf2::PBKDF2_HMAC_SHA512,
        n_iter,
        &salt,
        password.as_bytes(),
        &pbkdf2_hash,
    );
    let wrong_password = "Definitely not the correct password";
    let should_fail = pbkdf2::verify(
        pbkdf2::PBKDF2_HMAC_SHA512,
        n_iter,
        &salt,
        wrong_password.as_bytes(),
        &pbkdf2_hash,
    );

    assert!(should_succeed.is_ok());
    assert!(!should_fail.is_ok());

    Ok(())
}

Структуры данных

РецептКрейтыКатегории
Определение и работа с типами, представленными в виде битовых полейbitflags-badgecat-no-std-badge

Пользовательские структуры данных

Определение и работа с типами, представленными в виде битовых полей

bitflags-badge cat-no-std-badge

Пример создаёт тип MyFlags, представляющий собой типобезопасное битовое поле с помощью макроса bitflags! и реализует простую операцию clear и типаж Display для этого типа. Чуть ниже, пример показывает основные битовые операции и специальное для этого типа форматирование в строку.

#[macro_use]
extern crate bitflags;

use std::fmt;

bitflags! {
    struct MyFlags: u32 {
        const FLAG_A       = 0b00000001;
        const FLAG_B       = 0b00000010;
        const FLAG_C       = 0b00000100;
        const FLAG_ABC     = Self::FLAG_A.bits
                           | Self::FLAG_B.bits
                           | Self::FLAG_C.bits;
    }
}

impl MyFlags {
    pub fn clear(&mut self) -> &mut MyFlags {
        self.bits = 0;  
        self
    }
}

impl fmt::Display for MyFlags {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:032b}", self.bits)
    }
}

fn main() {
    let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C;
    let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C;
    assert_eq!((e1 | e2), MyFlags::FLAG_ABC);   
    assert_eq!((e1 & e2), MyFlags::FLAG_C);    
    assert_eq!((e1 - e2), MyFlags::FLAG_A);    
    assert_eq!(!e2, MyFlags::FLAG_A);           

    let mut flags = MyFlags::FLAG_ABC;
    assert_eq!(format!("{}", flags), "00000000000000000000000000000111");
    assert_eq!(format!("{}", flags.clear()), "00000000000000000000000000000000");
    assert_eq!(format!("{:?}", MyFlags::FLAG_B), "FLAG_B");
    assert_eq!(format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "FLAG_A | FLAG_B");
}

Базы данных

РецептКрейтыКатегории
Создание SQLite базы данныхrusqlite-badgecat-database-badge
Выборка и вставка данныхrusqlite-badgecat-database-badge
Создание таблиц в Postgrespostgres-badgecat-database-badge
Выборка и вставка данныхpostgres-badgecat-database-badge
Агрегирование данныхpostgres-badgecat-database-badge

SQLite

Создание базы данных SQLite

rusqlite-badge cat-database-badge

Пакет rusqlite позволяет работать с базами данных SQLite. Посмотрите этот пакет, если вам необходима компиляция под Windows.

Connection::open создаёт базу данных, если она ещё не существует.

extern crate rusqlite;

use rusqlite::{Connection, Result};
use rusqlite::NO_PARAMS;

fn main() -> Result<()> {
    let conn = Connection::open("cats.db")?;

    conn.execute(
        "create table if not exists cat_colors (
             id integer primary key,
             name text not null unique
         )",
        NO_PARAMS,
    )?;
    conn.execute(
        "create table if not exists cats (
             id integer primary key,
             name text not null,
             color_id integer not null references cat_colors(id)
         )",
        NO_PARAMS,
    )?;

    Ok(())
}

Выборка и вставка данных

rusqlite-badge cat-database-badge

Connection::open откроет базу данных cats, созданную в прошлом рецепте. Этот рецепт создаёт таблицы cat_colors и cats с помощью метода execute из Connection. Сначала данные вставляются в таблицу cat_colors. После того как запись о цвете вставилась, метод last_insert_rowid из Connection используется для получения id последней вставленной записи цвета. Этот id используется при вставке данных в таблицу cats. Затем подготавливается запрос на выборку с помощью метода prepare, который возвращает структуру statement. Затем выполняется запрос, используя метод query_map из statement.

extern crate rusqlite;

use rusqlite::NO_PARAMS;
use rusqlite::{Connection, Result};
use std::collections::HashMap;

#[derive(Debug)]
struct Cat {
    name: String,
    color: String,
}

fn main() -> Result<()> {
    let conn = Connection::open("cats.db")?;

    let mut cat_colors = HashMap::new();
    cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]);
    cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]);

    for (color, catnames) in &cat_colors {
        conn.execute(
            "INSERT INTO cat_colors (name) values (?1)",
            &[&color.to_string()],
        )?;
        let last_id: String = conn.last_insert_rowid().to_string();

        for cat in catnames {
            conn.execute(
                "INSERT INTO cats (name, color_id) values (?1, ?2)",
                &[&cat.to_string(), &last_id],
            )?;
        }
    }
    let mut stmt = conn.prepare(
        "SELECT c.name, cc.name from cats c
         INNER JOIN cat_colors cc
         ON cc.id = c.color_id;",
    )?;

    let cats = stmt.query_map(NO_PARAMS, |row| {
        Ok(Cat {
            name: row.get(0)?,
            color: row.get(1)?,
        })
    })?;

    for cat in cats {
        println!("Found cat {:?}", cat);
    }

    Ok(())
}

Использование транзакций

rusqlite-badge cat-database-badge

[Connection::open] откроет базу данных cats.db из первого рецепта.

Начните транзакцию с помощью Connection::transaction. Транзакции будут отменены, если они не будут явно закрыты с помощью Transaction::commit.

В следующем примере цвета добавляются в таблицу, которая имеет ограничение на уникальность для названия цвета. При попытке вставки дублирующего цвета транзакция откатывается.

extern crate rusqlite;

use rusqlite::{Connection, Result, NO_PARAMS};

fn main() -> Result<()> {
    let mut conn = Connection::open("cats.db")?;

    successful_tx(&mut conn)?;

    let res = rolled_back_tx(&mut conn);
    assert!(res.is_err());

    Ok(())
}

fn successful_tx(conn: &mut Connection) -> Result<()> {
    let tx = conn.transaction()?;

    tx.execute("delete from cat_colors", NO_PARAMS)?;
    tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
    tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;

    tx.commit()
}

fn rolled_back_tx(conn: &mut Connection) -> Result<()> {
    let tx = conn.transaction()?;

    tx.execute("delete from cat_colors", NO_PARAMS)?;
    tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
    tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;
    tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;

    tx.commit()
}

Работ с Postgres

Создание таблиц в Postgres

postgres-badge cat-database-badge

Используйте пакет postgres для создания таблиц в Postgres.

С помощью Client::connect можно присоединиться к существующей базе данных. Этот рецепт использует строку подключения в формате URL для передачи в Client::connect. Эта функция предполагает, что база данных существует, названа library, имя пользователя postgres и пароль postgres.

extern crate postgres;

 use postgres::{Client, NoTls, Error};

 fn main() -> Result<(), Error> {
     let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", NoTls)?;
     
     client.batch_execute("
         CREATE TABLE IF NOT EXISTS author (
             id              SERIAL PRIMARY KEY,
             name            VARCHAR NOT NULL,
             country         VARCHAR NOT NULL
             )
     ")?;

     client.batch_execute("
         CREATE TABLE IF NOT EXISTS book  (
             id              SERIAL PRIMARY KEY,
             title           VARCHAR NOT NULL,
             author_id       INTEGER NOT NULL REFERENCES author
             )
     ")?;

     Ok(())
 }

Вставка и чтение данных

postgres-badge cat-database-badge

Этот рецепт вставляет данные в таблицу author используя метод execute из типа Client. Затем выводит данные из той же таблицы author используя метод query снова из Client.

extern crate postgres;

use postgres::{Client, NoTls, Error};
use std::collections::HashMap;

struct Author {
    _id: i32,
    name: String,
    country: String
}

fn main() -> Result<(), Error> {
    let mut client = Client::connect("postgresql://postgres:postgres@localhost/library",
                                    NoTls)?;
    
    let mut authors = HashMap::new();
    authors.insert(String::from("Chinua Achebe"), "Nigeria");
    authors.insert(String::from("Rabindranath Tagore"), "India");
    authors.insert(String::from("Anita Nair"), "India");

    for (key, value) in &authors {
        let author = Author {
            _id: 0,
            name: key.to_string(),
            country: value.to_string()
        };

        client.execute(
                "INSERT INTO author (name, country) VALUES ($1, $2)",
                &[&author.name, &author.country],
        )?;
    }

    for row in client.query("SELECT id, name, country FROM author", &[])? {
        let author = Author {
            _id: row.get(0),
            name: row.get(1),
            country: row.get(2),
        };
        println!("Author {} is from {}", author.name, author.country);
    }

    Ok(())

}

Агрегирование данных

postgres-badge cat-database-badge

Этот рецепт выводит в порядке убывания национальности первых 7999 художников из базы данных "Museum of Modern Art".

extern crate postgres;
use postgres::{Client, Error, NoTls};

struct Nation {
    nationality: String,
    count: i64,
}

fn main() -> Result<(), Error> {
    let mut client = Client::connect(
        "postgresql://postgres:postgres@127.0.0.1/moma",
        NoTls,
    )?;

    for row in client.query
	("SELECT nationality, COUNT(nationality) AS count
	FROM artists GROUP BY nationality ORDER BY count DESC", &[])? {
        
        let (nationality, count) : (Option<String>, Option<i64>)
		= (row.get (0), row.get (1));
        
        if nationality.is_some () && count.is_some () {

            let nation = Nation{
                nationality: nationality.unwrap(),
                count: count.unwrap(),
        };
            println!("{} {}", nation.nationality, nation.count);
            
        }
    }

    Ok(())
}

Дата и время

РецептКрейтыКатегории
Измерение прошедшего времениstd-badgecat-time-badge
Проверяемое вычисление даты и времениchrono-badgecat-date-and-time-badge
Преобразование локального времени во время в другом часовом поясеchrono-badgecat-date-and-time-badge
Просмотр даты и времениchrono-badgecat-date-and-time-badge
Преобразование даты в UNIX-timestamp и обратноchrono-badgecat-date-and-time-badge
Форматированный вывод даты и времениchrono-badgecat-date-and-time-badge
Разбор строки в структуру DateTimechrono-badgecat-date-and-time-badge

Длительность и вычисление интервалов

Измерение прошедшего времени между двумя моментами выполнения кода

std-badge cat-time-badge

Пример измеряет количество времени time::Instant::elapsed, прошедшее с момента time::Instant::now.

Вызов time::Instant::elapsed возвращает time::Duration, который мы выводим на экран в конце примера. Этот метод не изменяет и не сбрасывает состояние объекта time::Instant.

use std::time::{Duration, Instant};
use std::thread;

fn expensive_function() {
    thread::sleep(Duration::from_secs(1));
}

fn main() {
    let start = Instant::now();
    expensive_function();
    let duration = start.elapsed();

    println!("Time elapsed in expensive_function() is: {:?}", duration);
}

Проверяемое вычисление даты и времени

chrono-badge cat-date-and-time-badge

Код в примере вычисляет и отображает дату и время через 2 недели от текущего момента времени используя DateTime::checked_add_signed и дату за сутки до этого дня используя DateTime::checked_sub_signed. Эти методы возвращают None, если дата и время не могут быть вычислены.

Доступные управляющие последовательности для DateTime::format могут быть найдены в chrono::format::strftime.

extern crate chrono;
use chrono::{DateTime, Duration, Utc};

fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> {
    date_time.checked_sub_signed(Duration::days(1))
}

fn main() {
    let now = Utc::now();
    println!("{}", now);

    let almost_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2))
            .and_then(|in_2weeks| in_2weeks.checked_add_signed(Duration::weeks(1)))
            .and_then(day_earlier);

    match almost_three_weeks_from_now {
        Some(x) => println!("{}", x),
        None => eprintln!("Almost three weeks from now overflows!"),
    }

    match now.checked_add_signed(Duration::max_value()) {
        Some(x) => println!("{}", x),
        None => eprintln!("We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center."),
    }
}

Преобразование локального времени во время в другом часовом поясе

chrono-badge cat-date-and-time-badge

Этот пример получает локальное время и отображает его с помощью offset::Local::now и затем преобразует его в UTC используя метод DateTime::from_utc. Затем время преобразуется посредством структуры offset::FixedOffset и время из UTC затем преобразуется в UTC+8 и UTC-2.

extern crate chrono;

use chrono::{DateTime, FixedOffset, Local, Utc};

fn main() {
    let local_time = Local::now();
    let utc_time = DateTime::<Utc>::from_utc(local_time.naive_utc(), Utc);
    let china_timezone = FixedOffset::east(8 * 3600);
    let rio_timezone = FixedOffset::west(2 * 3600);
    println!("Local time now is {}", local_time);
    println!("UTC time now is {}", utc_time);
    println!(
        "Time in Hong Kong now is {}",
        utc_time.with_timezone(&china_timezone)
    );
    println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone));
}

Разбор и отображение

Просмотр даты и времени

chrono-badge cat-date-and-time-badge

Код получает текущее DateTime в UTC, его час/минуты/секунды посредством типажа Timelike и его год/месяц/день/день недели посредством типажа Datelike.

extern crate chrono;
use chrono::{Datelike, Timelike, Utc};

fn main() {
    let now = Utc::now();

    let (is_pm, hour) = now.hour12();
    println!(
        "The current UTC time is {:02}:{:02}:{:02} {}",
        hour,
        now.minute(),
        now.second(),
        if is_pm { "PM" } else { "AM" }
    );
    println!(
        "And there have been {} seconds since midnight",
        now.num_seconds_from_midnight()
    );

    let (is_common_era, year) = now.year_ce();
    println!(
        "The current UTC date is {}-{:02}-{:02} {:?} ({})",
        year,
        now.month(),
        now.day(),
        now.weekday(),
        if is_common_era { "CE" } else { "BCE" }
    );
    println!(
        "And the Common Era began {} days ago",
        now.num_days_from_ce()
    );
}

Преобразование даты в UNIX-timestamp и обратно

chrono-badge cat-date-and-time-badge

Пример преобразует дату, переданную через NaiveDate::from_ymd и NaiveTime::from_hms во временную метку UNIX (UNIX timestemp) используя метод NaiveDateTime::timestamp. Затем он вычисляет дату, соответствующую одному миллиарду секунд, прошедших с 1 Января, 1970 UTC при помощи функции NaiveDateTime::from_timestamp.

extern crate chrono;

use chrono::{NaiveDate, NaiveDateTime};

fn main() {
    let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44);
    println!(
        "Number of seconds between 1970-01-01 00:00:00 and {} is {}.",
        date_time, date_time.timestamp());

    let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0);
    println!(
        "Date after a billion seconds since 1970-01-01 00:00:00 was {}.",
        date_time_after_a_billion_seconds);
}

Форматированный вывод даты и времени

chrono-badge cat-date-and-time-badge

Пример получает и отображает текущее время в UTC используя метод Utc::now. Форматирует текущее время в распространённых форматах RFC 2822 посредством DateTime::to_rfc2822, RFC 3339 посредством DateTime::to_rfc3339, и пользовательском формате используя DateTime::format.

extern crate chrono;
use chrono::{DateTime, Utc};

fn main() {
    let now: DateTime<Utc> = Utc::now();

    println!("UTC now is: {}", now);
    println!("UTC now in RFC 2822 is: {}", now.to_rfc2822());
    println!("UTC now in RFC 3339 is: {}", now.to_rfc3339());
    println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y"));
}

Разбор строки в структуру DateTime

chrono-badge cat-date-and-time-badge

Код совершает разбор в структуру DateTime строк, представляющих распространённые форматы RFC 2822, RFC 3339 и пользовательский формат. Разбор использует функции DateTime::parse_from_rfc2822, DateTime::parse_from_rfc3339, и DateTime::parse_from_str соответственно.

Управляющие последовательности, которые доступны для DateTime::parse_from_str, могут быть найдены в документации к chrono::format::strftime. Заметим, что функция DateTime::parse_from_str требует, что такая структура DateTime должна иметь возможность быть созданной так, что она однозначно определяет дату и время. Для парсинга дат и времён без временных зон мы используем NaiveDate, NaiveTime и NaiveDateTime.

extern crate chrono;
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime};
use chrono::format::ParseError;


fn main() -> Result<(), ParseError> {
    let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?;
    println!("{}", rfc2822);

    let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?;
    println!("{}", rfc3339);

    let custom = DateTime::parse_from_str("5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")?;
    println!("{}", custom);

    let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?;
    println!("{}", time_only);

    let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?;
    println!("{}", date_only);

    let no_timezone = NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")?;
    println!("{}", no_timezone);

    Ok(())
}

Средства разработки

Отладка

Версионирование

РецептКрейтыКатегории
Разбор и увеличение номера версииsemver-badgecat-config-badge
Разбор сложной строки с версиейsemver-badgecat-config-badge
Проверка если версия является предварительным релизомsemver-badgecat-config-badge
Нахождение последней версии в заданном диапазонеsemver-badgecat-config-badge
Проверка версии внешней команды для совместимостиsemver-badgecat-text-processing-badge cat-os-badge

Время сборки

РецептКрейтыКатегории
Компиляция и статическая линковка с библиотекой на языке Ccc-badgecat-development-tools-badge
Компиляция и статическая линковка с библиотекой на языке C++cc-badgecat-development-tools-badge
Компиляция библиотеки на языке C с особыми определениями препроцессораcc-badgecat-development-tools-badge

Отладка

Диагностические сообщения

Вывод отладочного сообщения в консоль

log-badge env_logger-badge cat-debugging-badge

Крейт log обеспечивает инструменты для логирования. Крейт env_logger конфигурирует логирование с помощью переменных окружения. Макрос debug! работает подобно другим макросам, принимая строки форматирования по образцу std::fmt.

#[macro_use]
extern crate log;
extern crate env_logger;

fn execute_query(query: &str) {
    debug!("Executing query: {}", query);
}

fn main() {
    env_logger::init();

    execute_query("DROP TABLE students");
}

Ничего не выводится после запуска этого кода. По умолчанию, уровень логирования установлен в error, и все сообщения более низкого уровня отбрасываются.

Установка переменной окружения RUST_LOG включает печать:

$ RUST_LOG=debug cargo run

Сначала Cargo печатает отладочную информацию, и затем будет напечатана строка в самом конце вывода на экран:

DEBUG:main: Executing query: DROP TABLE students

Вывод сообщения об ошибке в консоль

log-badge env_logger-badge cat-debugging-badge

Правильная обработка ошибок рассматривает исключительные ситуации как, ну... исключительные. Здесь ошибка выводится в stderr с помощью удобного макроса error!, определённого в крейте log.

#[macro_use]
extern crate log;
extern crate env_logger;

fn execute_query(_query: &str) -> Result<(), &'static str> {
    Err("I'm afraid I can't do that")
}

fn main() {
    env_logger::init();

    let response = execute_query("DROP TABLE students");
    if let Err(err) = response {
        error!("Failed to execute query: {}", err);
    }
}

Вывод в stdout вместо stderr

log-badge env_logger-badge cat-debugging-badge

Пример создаёт особую конфигурацию логгера используя Builder::target, чтобы установить стандартный вывод для логирования в Target::Stdout.

#[macro_use]
extern crate log;
extern crate env_logger;

use env_logger::{Builder, Target};

fn main() {
    Builder::new()
        .target(Target::Stdout)
        .init();

    error!("This error has been printed to Stdout");
}

Логирование сообщений с помощью пользовательского логгера

log-badge cat-debugging-badge

Реализует особый логгер в консоль ConsoleLogger, который печатает в стандартный вывод. Чтобы иметь возможность использовать макросы для логгеров, ConsoleLogger реализует типаж log::Log. Вызов функции log::set_logger подключает логгер.

#[macro_use]
extern crate log;

use log::{Record, Level, Metadata, LevelFilter, SetLoggerError};

static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger;

struct ConsoleLogger;

impl log::Log for ConsoleLogger {
  fn enabled(&self, metadata: &Metadata) -> bool {
     metadata.level() <= Level::Info
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!("Rust says: {} - {}", record.level(), record.args());
        }
    }

    fn flush(&self) {}
}

fn main() -> Result<(), SetLoggerError> {
    log::set_logger(&CONSOLE_LOGGER)?;
    log::set_max_level(LevelFilter::Info);

    info!("hello log");
    warn!("warning");
    error!("oops");
    Ok(())
}

Логирование в Unix syslog

log-badge syslog-badge cat-debugging-badge

Код в примере выводит сообщения в UNIX syslog. Инициализируется движок логирования через syslog::init. Объект syslog::Facility регистрирует программу путём передачи класса логирования, log::LevelFilter определяет уровень логирования, через Option<&str> передаётся необязательное имя программы.

#[macro_use]
extern crate log;
#[cfg(target_os = "linux")]
extern crate syslog;

#[cfg(target_os = "linux")]
use syslog::{Facility, Error};

#[cfg(target_os = "linux")]
fn main() -> Result<(), Error> {
    syslog::init(Facility::LOG_USER,
                 log::LevelFilter::Debug,
                 Some("My app name"))?;
    debug!("this is a debug {}", "message");
    error!("this is an error!");
    Ok(())
}

#[cfg(not(target_os = "linux"))]
fn main() {
    println!("So far, only Linux systems are supported.");
}

Конфигурация логирования

Включение уровней логирования для каждого модуля

log-badge env_logger-badge cat-debugging-badge

Крейты с двумя модулями foo и вложенным foo::bar с директивами логирования, управляемыми отдельно с помощью переменной окружения RUST_LOG.

#[macro_use]
extern crate log;
extern crate env_logger;

mod foo {
    mod bar {
        pub fn run() {
            warn!("[bar] warn");
            info!("[bar] info");
            debug!("[bar] debug");
        }
    }

    pub fn run() {
        warn!("[foo] warn");
        info!("[foo] info");
        debug!("[foo] debug");
        bar::run();
    }
}

fn main() {
    env_logger::init();
    warn!("[root] warn");
    info!("[root] info");
    debug!("[root] debug");
    foo::run();
}

Переменная среды RUST_LOG управляет выводом env_logger. Объявления модуля принимают разделённые запятыми записи, отформатированные как path::to::module=log_level. Запустите приложение test следующим образом:

RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test

Устанавливает по умолчанию log::Level в warn, модуль foo и модуль foo::bar в info и debug.

WARN:test: [root] warn
WARN:test::foo: [foo] warn
INFO:test::foo: [foo] info
WARN:test::foo::bar: [bar] warn
INFO:test::foo::bar: [bar] info
DEBUG:test::foo::bar: [bar] debug

Использование переменной среды для настройки логирования

log-badge env_logger-badge cat-debugging-badge

Структура Builder настраивает логирование.

Структура Builder::parse анализирует содержимое переменной среды MY_APP_LOG в форме синтаксиса RUST_LOG. Затем Builder::init инициализирует логгер. Все эти шаги обычно выполняются внутри с помощью env_logger::init.

#[macro_use]
extern crate log;
extern crate env_logger;

use std::env;
use env_logger::Builder;

fn main() {
    Builder::new()
        .parse(&env::var("MY_APP_LOG").unwrap_or_default())
        .init();

    info!("informational message");
    warn!("warning message");
    error!("this is an error {}", "message");
}

Включить метку времени в сообщения лога

log-badge env_logger-badge chrono-badge cat-debugging-badge

Создаёт пользовательскую конфигурацию логгера с помощью Builder. Каждая запись в журнале вызывает Local::now для получения текущего DateTime в местном часовом поясе и использует метод DateTime::format со strftime::specifiers для форматирования метки времени, используемой в конечном журнале.

В примере вызывается Builder::format для установки замыкания, которое форматирует каждый текст сообщения с отметкой времени Record::level и телом (Record::args).

#[macro_use]
extern crate log;
extern crate chrono;
extern crate env_logger;

use std::io::Write;
use chrono::Local;
use env_logger::Builder;
use log::LevelFilter;

fn main() {
    Builder::new()
        .format(|buf, record| {
            writeln!(buf,
                "{} [{}] - {}",
                Local::now().format("%Y-%m-%dT%H:%M:%S"),
                record.level(),
                record.args()
            )
        })
        .filter(None, LevelFilter::Info)
        .init();

    warn!("warn");
    info!("info");
    debug!("debug");
}

вывод stderr будет содержать

2017-05-22T21:57:06 [WARN] - warn
2017-05-22T21:57:06 [INFO] - info

Логирование сообщений в пользовательское место

log-badge log4rs-badge cat-debugging-badge

Крейт log4rs настраивает вывод журнала в пользовательское место. log4rs может использовать либо внешний файл YAML, либо программную конфигурацию.

Создайте конфигурацию логов с помощью log4rs::append::file::FileAppender. Appender определяет место назначения логирования. Конфигурация продолжается кодированием формата одной записи с использованием пользовательского шаблона из log4rs::encode::pattern. Конфигурация присваивается log4rs::config::Config и устанавливается по умолчанию log::LevelFilter.

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
extern crate log4rs;

use log::LevelFilter;
use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Root};

error_chain! {
    foreign_links {
        Io(std::io::Error);
        LogConfig(log4rs::config::Errors);
        SetLogger(log::SetLoggerError);
    }
}

fn main() -> Result<()> {
    let logfile = FileAppender::builder()
        .encoder(Box::new(PatternEncoder::new("{l} - {m}\n")))
        .build("log/output.log")?;

    let config = Config::builder()
        .appender(Appender::builder().build("logfile", Box::new(logfile)))
        .build(Root::builder()
                   .appender("logfile")
                   .build(LevelFilter::Info))?;

    log4rs::init_config(config)?;

    info!("Hello, world!");

    Ok(())
}

Версионирование

Анализ и инкремент строки с версией

semver-badge cat-config-badge

Пример строит semver::Version из строкового значения используя Version::parse, затем осуществляет инкремент номера патча, минорного и мажорного номера версии один за одним.

Заметим, что в соответствии со спецификацией Semantic Versioning Specification увеличение минорного номера версии сбрасывает счётчик патча на 0. Аналогично увеличение мажорного номера версии сбрасывает и минорный номер и номер патча на 0.

extern crate semver;

use semver::{Version, SemVerError};

fn main() -> Result<(), SemVerError> {
    let mut parsed_version = Version::parse("0.2.6")?;

    assert_eq!(
        parsed_version,
        Version {
            major: 0,
            minor: 2,
            patch: 6,
            pre: vec![],
            build: vec![],
        }
    );

    parsed_version.increment_patch();
    assert_eq!(parsed_version.to_string(), "0.2.7");
    println!("New patch release: v{}", parsed_version);

    parsed_version.increment_minor();
    assert_eq!(parsed_version.to_string(), "0.3.0");
    println!("New minor release: v{}", parsed_version);

    parsed_version.increment_major();
    assert_eq!(parsed_version.to_string(), "1.0.0");
    println!("New major release: v{}", parsed_version);

    Ok(())
}

Разбор строки содержащей сложную версию

semver-badge cat-config-badge

Строит semver::Version из строки используя Version::parse. Строка содержит номер предварительного релиза и метаданные сборки как определяется в спецификации Semantic Versioning Specification.

Заметим, что в соответствии со спецификации метаданные сборки анализируются, но не принимаются во внимание в процессе сравнения версий. Другими словами, две версии могут быть равны даже если их данные сборки различаются.

extern crate semver;

use semver::{Identifier, Version, SemVerError};

fn main() -> Result<(), SemVerError> {
    let version_str = "1.0.49-125+g72ee7853";
    let parsed_version = Version::parse(version_str)?;

    assert_eq!(
        parsed_version,
        Version {
            major: 1,
            minor: 0,
            patch: 49,
            pre: vec![Identifier::Numeric(125)],
            build: vec![],
        }
    );
    assert_eq!(
        parsed_version.build,
        vec![Identifier::AlphaNumeric(String::from("g72ee7853"))]
    );

    let serialized_version = parsed_version.to_string();
    assert_eq!(&serialized_version, version_str);

    Ok(())
}

Проверка того, является ли версия предварительным релизом

semver-badge cat-config-badge

Даны две версии, is_prerelease проверяет, что одна является предварительным релизом, а вторая - нет.

extern crate semver;

use semver::{Version, SemVerError};

fn main() -> Result<(), SemVerError> {
    let version_1 = Version::parse("1.0.0-alpha")?;
    let version_2 = Version::parse("1.0.0")?;

    assert!(version_1.is_prerelease());
    assert!(!version_2.is_prerelease());

    Ok(())
}

Нахождение самой последней версии из заданного диапазона

semver-badge cat-config-badge

Дан список версий в виде &str, найти самую последнюю версию semver::Version. Тип semver::VersionReq фильтрует список с помощью метода VersionReq::matches. Также демонстрируется semver настройки для предварительного релиза.

#[macro_use]
extern crate error_chain;
extern crate semver;

use semver::{Version, VersionReq};

error_chain! {
    foreign_links {
        SemVer(semver::SemVerError);
        SemVerReq(semver::ReqParseError);
    }
}

fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result<Option<Version>>
where
    I: IntoIterator<Item = &'a str>,
{
    let vreq = VersionReq::parse(version_req_str)?;

    Ok(
        iterable
            .into_iter()
            .filter_map(|s| Version::parse(s).ok())
            .filter(|s| vreq.matches(s))
            .max(),
    )
}

fn main() -> Result<()> {
    assert_eq!(
        find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?,
        Some(Version::parse("1.0.0")?)
    );

    assert_eq!(
        find_max_matching_version(
            ">1.2.3-alpha.3",
            vec![
                "1.2.3-alpha.3",
                "1.2.3-alpha.4",
                "1.2.3-alpha.10",
                "1.2.3-beta.4",
                "3.4.5-alpha.9",
            ]
        )?,
        Some(Version::parse("1.2.3-beta.4")?)
    );

    Ok(())
}

Проверка версии внешней команды на совместимость

semver-badge cat-text-processing-badge cat-os-badge

Код в рецепте выполняет git --version используя Command а затем анализирует номер версии и преобразует в semver::Version используя Version::parse. Метод VersionReq::matches сравнивает требуемую версию semver::VersionReq с только что полученной версией. Вывод программы напоминает результат "git version x.y.z".

#[macro_use]
extern crate error_chain;
extern crate semver;

use std::process::Command;
use semver::{Version, VersionReq};

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Utf8(std::string::FromUtf8Error);
        SemVer(semver::SemVerError);
        SemVerReq(semver::ReqParseError);
    }
}

fn main() -> Result<()> {
    let version_constraint = "> 1.12.0";
    let version_test = VersionReq::parse(version_constraint)?;
    let output = Command::new("git").arg("--version").output()?;

    if !output.status.success() {
        bail!("Command executed with failing error code");
    }

    let stdout = String::from_utf8(output.stdout)?;
    let version = stdout.split(" ").last().ok_or_else(|| {
        "Invalid command output"
    })?;
    let parsed_version = Version::parse(version)?;

    if !version_test.matches(&parsed_version) {
        bail!("Command version lower than minimum supported version (found {}, need {})",
            parsed_version, version_constraint);
    }

    Ok(())
}

Инструменты времени компиляции

Этот раздел охватывает инструменты времени компиляции, или код который запускается до момента компиляции исходного кода крейта. По соглашению, код исполняемый во время сборки находится в файле build.rs и он часто называется "скриптом сборки". Распространённые случаи использования включают генерацию Rust кода и компиляцию связанного C/C++/asm кода. Смотрите документацию по этому вопросу на crates.io для дополнительной информации.

Компиляция и статическая компоновка с идущей в комплекте библиотекой на C

cc-badge cat-development-tools-badge

Чтобы покрыть сценарии, где требуется использовать дополнительный код на C, C++ или ассемблере, есть крейт cc, который предлагает простой API для компиляции включённого в проект кода на C/C++/asm в статические библиотеки (.a), которые затем могут быть статически скомпонованы rustc.

Следующий пример содержит некоторый код на C (src/hello.c) который будет использован в Rust коде. Перед компиляцией кода на Rust файл запускается специальный "build" файл (build.rs), определённый в Cargo.toml. Используя крейт cc будет создана статическая библиотека (в данном случае libhello.a, смотрите compile docs), которая затем может быть использована из кода на Rust с помощью декларации сигнатуры внешней функции в блоке extern.

Поскольку включённый код на C очень простой, нужен только один файл с исходным кодом нужен для передачи в cc::Build. В случае более сложных сценариев, cc::Build предлагает полный набор возможностей для определения include путей и флагов flag для внешнего компилятора.

Cargo.toml

[package]
...
build = "build.rs"

[build-dependencies]
cc = "1"

[dependencies]
error-chain = "0.11"

build.rs

extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/hello.c")
        .compile("hello");   // outputs `libhello.a`
}

src/hello.c

#include <stdio.h>


void hello() {
    printf("Hello from C!\n");
}

void greet(const char* name) {
    printf("Hello, %s!\n", name);
}

src/main.rs

#[macro_use] extern crate error_chain;
use std::ffi::CString;
use std::os::raw::c_char;

error_chain! {
    foreign_links {
        NulError(::std::ffi::NulError);
        Io(::std::io::Error);
    }
}

fn prompt(s: &str) -> Result<String> {
    use std::io::Write;
    print!("{}", s);
    std::io::stdout().flush()?;
    let mut input = String::new();
    std::io::stdin().read_line(&mut input)?;
    Ok(input.trim().to_string())
}

extern {
    fn hello();
    fn greet(name: *const c_char);
}

fn main() -> Result<()> {
    unsafe { hello() }
    let name = prompt("What's your name? ")?;
    let c_name = CString::new(name)?;
    unsafe { greet(c_name.as_ptr()) }
    Ok(())
}

Компиляция и статическая компоновка с идущей в комплекте библиотекой на C++

cc-badge cat-development-tools-badge

Компоновка с библиотекой на C++ очень похожа на компоновку с библиотекой на C. Самые главные два отличия это когда вам нужно компилировать и компоновать библиотеку на C++ вы должны определить компилятор C++ с помощью специального метода cpp(true) и также нужно предотвратить искажение имён компилятором C++ путём добавления extern "C" секции на самый верхний уровень в исходном коде C++.

Cargo.toml

[package]
...
build = "build.rs"

[build-dependencies]
cc = "1"

build.rs

extern crate cc;

fn main() {
    cc::Build::new()
        .cpp(true)
        .file("src/foo.cpp")
        .compile("foo");   
}

src/foo.cpp

extern "C" {
    int multiply(int x, int y);
}

int multiply(int x, int y) {
    return x*y;
}

src/main.rs

extern {
    fn multiply(x : i32, y : i32) -> i32;
}

fn main(){
    unsafe {
        println!("{}", multiply(5,7));
    }   
}

Компиляция библиотеки на C с использованием особых директив

cc-badge cat-development-tools-badge

На самом деле довольно просто собрать код на C с особыми директивами используя cc::Build::define. Этот метод принимает значение Option, таким образом делая возможным определить такие директивы, как, скажем, #define APP_NAME "foo" или там #define WELCOME (можно передать None для определения директивы без значения). Этот пример собирает файл на C с динамически определёнными директивами, установленными в build.rs и печатает "Welcome to foo - version 1.0.2" после запуска. Cargo устанавливает некоторые переменные окружения, которые также могут быть полезны для динамического определения директив.

Cargo.toml

[package]
...
version = "1.0.2"
build = "build.rs"

[build-dependencies]
cc = "1"

build.rs

extern crate cc;

fn main() {
    cc::Build::new()
        .define("APP_NAME", "\"foo\"")
        .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str())
        .define("WELCOME", None)
        .file("src/foo.c")
        .compile("foo");
}

src/foo.c

#include <stdio.h>

void print_app_info() {
#ifdef WELCOME
    printf("Welcome to ");
#endif
    printf("%s - version %s\n", APP_NAME, VERSION);
}

src/main.rs

extern {
    fn print_app_info();
}

fn main(){
    unsafe {
        print_app_info();
    }   
}

Кодирование

РецептКрейтыКатегории
%-кодирование строки![percent-encoding-badge]cat-encoding-badge
Кодирование строки как application/x-www-form-urlencodedurl-badgecat-encoding-badge
Кодирование и декодирование шестнадцатеричного представленияdata-encoding-badgecat-encoding-badge
Кодирование и декодирования представления в base64base64-badgecat-encoding-badge
Чтение записей из CSVcsv-badgecat-encoding-badge
Чтение записей из CSV с другим разделителемcsv-badgecat-encoding-badge
Фильтрование записей из CSV, удовлетворяющему предикатуcsv-badgecat-encoding-badge
Обработка неправильных данных в CSV с помощью Serdecsv-badge serde-badgecat-encoding-badge
Сериализация записей в CSVcsv-badgecat-encoding-badge
Сериализация записей в CSV с помощью Serdecsv-badge serde-badgecat-encoding-badge
Преобразование столбца в CSV-файлеcsv-badge serde-badgecat-encoding-badge
Сериализация и десериализация неструктурированных данных в формате JSONserde-json-badgecat-encoding-badge
Десериализация TOML-файлаtoml-badgecat-encoding-badge
Чтение и запись целых числах в прямом порядке байтов (little-endian)byteorder-badgecat-encoding-badge

Наборы символов

Процент (или URL) кодирование строки

![percent-encoding-badge] cat-encoding-badge

Кодирование входной строки с помощью percent-encoding, используя utf8_percent_encode из крейтаpercent-encoding. Затем выполнение декодирования с помощью функции percent_decode.

extern crate percent_encoding;

use percent_encoding::{utf8_percent_encode, percent_decode, AsciiSet, CONTROLS};
use std::str::Utf8Error;

/// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');

fn main() -> Result<(), Utf8Error> {
    let input = "confident, productive systems programming";

    let iter = utf8_percent_encode(input, FRAGMENT);
    let encoded: String = iter.collect();
    assert_eq!(encoded, "confident,%20productive%20systems%20programming");

    let iter = percent_decode(encoded.as_bytes());
    let decoded = iter.decode_utf8()?;
    assert_eq!(decoded, "confident, productive systems programming");

    Ok(())
}

Набор кодирования определяет, какие байты (в дополнение к не-ASCII и символам управления) должны кодироваться в процентах. Выбор этого набора зависит от контекста. Например, url кодирует ? в пути URL, но не в строке запроса.

Возвращаемое значение кодировки - это итератор срезов &str, которые собираются в тип String.

Кодирование строки в application/x-www-form-urlencoded

url-badge cat-encoding-badge

Кодирует строку в синтаксис application/x-www-form-urlencoded используя form_urlencoded::byte_serialize, а затем декодирует её с помощью form_urlencoded::parse. Обе функции возвращают итераторы, которые преобразуются в String.

extern crate url;
use url::form_urlencoded::{byte_serialize, parse};

fn main() {
    let urlencoded: String = byte_serialize("What is ❤?".as_bytes()).collect();
    assert_eq!(urlencoded, "What+is+%E2%9D%A4%3F");
    println!("urlencoded:'{}'", urlencoded);

    let decoded: String = parse(urlencoded.as_bytes())
        .map(|(key, val)| [key, val].concat())
        .collect();
    assert_eq!(decoded, "What is ❤?");
    println!("decoded:'{}'", decoded);
}

Кодирование и декодирование шестнадцатеричного кода

data-encoding-badge cat-encoding-badge

Крейт data_encoding предоставляет метод HEXUPPER::encode, который принимает &[u8] и возвращает String содержащую шестнадцатеричное представление данных.

Аналогичным образом предоставляется метод HEXUPPER::decode, который принимает &[u8] и возвращает Vec<u8>, если входные данные успешно декодированы.

Пример ниже конвертирует данные &[u8] в шестнадцатеричный эквивалент. Сравнивает эти значение с ожидаемым значением.

extern crate data_encoding;

use data_encoding::{HEXUPPER, DecodeError};

fn main() -> Result<(), DecodeError> {
    let original = b"The quick brown fox jumps over the lazy dog.";
    let expected = "54686520717569636B2062726F776E20666F78206A756D7073206F76\
        657220746865206C617A7920646F672E";

    let encoded = HEXUPPER.encode(original);
    assert_eq!(encoded, expected);

    let decoded = HEXUPPER.decode(&encoded.into_bytes())?;
    assert_eq!(&decoded[..], &original[..]);

    Ok(())
}

Кодирование и декодирование base64

base64-badge cat-encoding-badge

Кодирует байтовый срез в строку base64 с использованием функции encode и декодирует её с помощью функции decode.

#[macro_use]
extern crate error_chain;
extern crate base64;

use std::str;
use base64::{encode, decode};

error_chain! {
    foreign_links {
        Base64(base64::DecodeError);
        Utf8Error(str::Utf8Error);
    }
}

fn main() -> Result<()> {
    let hello = b"hello rustaceans";
    let encoded = encode(hello);
    let decoded = decode(&encoded)?;

    println!("origin: {}", str::from_utf8(hello)?);
    println!("base64 encoded: {}", encoded);
    println!("back to origin: {}", str::from_utf8(&decoded)?);

    Ok(())
}

Обработка CSV

Чтение CSV записей

csv-badge cat-encoding-badge

Считывает стандартные CSV записи в csv::StringRecord - слабо типизированное представление данных, которое ожидает допустимые строки UTF-8. В качестве альтернативы, csv::ByteRecord не делает никаких предположений об UTF-8.

extern crate csv;
use csv::Error;

fn main() -> Result<(), Error> {
    let csv = "year,make,model,description
		1948,Porsche,356,Luxury sports car
		1967,Ford,Mustang fastback 1967,American car";

    let mut reader = csv::Reader::from_reader(csv.as_bytes());
    for record in reader.records() {
        let record = record?;
        println!(
            "In {}, {} built the {} model. It is a {}.",
            &record[0],
            &record[1],
            &record[2],
            &record[3]
        );
    }

    Ok(())
}

Serde может десериализовать данные в строго типизированные структуры. Смотрите на метод csv::Reader::deserialize.

extern crate csv;
#[macro_use]
extern crate serde_derive;

#[derive(Deserialize)]
struct Record {
    year: u16,
    make: String,
    model: String,
    description: String,
}

fn main() -> Result<(), csv::Error> {
    let csv = "year,make,model,description
1948,Porsche,356,Luxury sports car
1967,Ford,Mustang fastback 1967,American car";

    let mut reader = csv::Reader::from_reader(csv.as_bytes());

    for record in reader.deserialize() {
        let record: Record = record?;
        println!(
            "In {}, {} built the {} model. It is a {}.",
            record.year,
            record.make,
            record.model,
            record.description
        );
    }

    Ok(())
}

Чтение CSV записей с другим разделителем

csv-badge cat-encoding-badge

Читает записи CSV с delimiter.

extern crate csv;
use csv::Error;
#[macro_use]
extern crate serde_derive;

#[derive(Debug, Deserialize)]
struct Record {
    name: String,
    place: String,
    #[serde(deserialize_with = "csv::invalid_option")]
    id: Option<u64>,
}

use csv::ReaderBuilder;

fn main() -> Result<(), Error> {
    let data = "name\tplace\tid
		Mark\tMelbourne\t46
		Ashley\tZurich\t92";

    let mut reader = ReaderBuilder::new().delimiter(b'\t').from_reader(data.as_bytes());
    for result in reader.deserialize::<Record>() {
        println!("{:?}", result?);
    }

    Ok(())
}

Фильтрация CSV записей, соответствующих предикату

csv-badge cat-encoding-badge

Возвращает только строки из data с полем, которое соответствует query .

#[macro_use]
extern crate error_chain;
extern crate csv;

use std::io;

error_chain!{
    foreign_links {
        Io(std::io::Error);
        CsvError(csv::Error);
    }
}

fn main() -> Result<()> {
    let query = "CA";
    let data = "\
City,State,Population,Latitude,Longitude
Kenai,AK,7610,60.5544444,-151.2583333
Oakman,AL,,33.7133333,-87.3886111
Sandfort,AL,,32.3380556,-85.2233333
West Hollywood,CA,37031,34.0900000,-118.3608333";

    let mut rdr = csv::ReaderBuilder::new().from_reader(data.as_bytes());
    let mut wtr = csv::Writer::from_writer(io::stdout());

    wtr.write_record(rdr.headers()?)?;

    for result in rdr.records() {
        let record = result?;
        if record.iter().any(|field| field == query) {
            wtr.write_record(&record)?;
        }
    }

    wtr.flush()?;
    Ok(())
}

Отказ от ответственности: этот пример был адаптирован из the csv crate tutorial.

Обработка неверных CSV данных с помощью Serde

csv-badge serde-badge cat-encoding-badge

Файлы CSV часто содержат неверные данные. Для этих случаев крейт csv предоставляет специальный десериализатор csv::invalid_option, который автоматически преобразует недопустимые данные в значения None.

extern crate csv;
use csv::Error;
#[macro_use]
extern crate serde_derive;

#[derive(Debug, Deserialize)]
struct Record {
    name: String,
    place: String,
    #[serde(deserialize_with = "csv::invalid_option")]
    id: Option<u64>,
}

fn main() -> Result<(), Error> {
    let data = "name,place,id
mark,sydney,46.5
ashley,zurich,92
akshat,delhi,37
alisha,colombo,xyz";

    let mut rdr = csv::Reader::from_reader(data.as_bytes());
    for result in rdr.deserialize() {
        let record: Record = result?;
        println!("{:?}", record);
    }

    Ok(())
}

Сериализация записей в CSV формат

csv-badge cat-encoding-badge

В этом примере показано, как сериализовать Rust кортеж. csv::writer поддерживает автоматическую сериализацию Rust типов в CSV записи. Метод write_record записывает простую запись, содержащую только строковые данные. Данные с более сложными значениями, такими как числа, числа с плавающей запятой и перечисления используют метод serialize. Поскольку CSV writer использует внутренний буфер, всегда явно выполняйте flush.

#[macro_use]
extern crate error_chain;
extern crate csv;

use std::io;

error_chain! {
    foreign_links {
        CSVError(csv::Error);
        IOError(std::io::Error);
   }
}

fn main() -> Result<()> {
    let mut wtr = csv::Writer::from_writer(io::stdout());

    wtr.write_record(&["Name", "Place", "ID"])?;

    wtr.serialize(("Mark", "Sydney", 87))?;
    wtr.serialize(("Ashley", "Dublin", 32))?;
    wtr.serialize(("Akshat", "Delhi", 11))?;

    wtr.flush()?;
    Ok(())
}

Сериализация записей в CSV с использованием Serde

csv-badge serde-badge cat-encoding-badge

В следующем примере показано, как сериализовать пользовательские структуры в виде записей CSV, используя крейт serde.

#[macro_use]
extern crate error_chain;
extern crate csv;
#[macro_use]
extern crate serde_derive;

use std::io;

error_chain! {
   foreign_links {
       IOError(std::io::Error);
       CSVError(csv::Error);
   }
}

#[derive(Serialize)]
struct Record<'a> {
    name: &'a str,
    place: &'a str,
    id: u64,
}

fn main() -> Result<()> {
    let mut wtr = csv::Writer::from_writer(io::stdout());

    let rec1 = Record { name: "Mark", place: "Melbourne", id: 56};
    let rec2 = Record { name: "Ashley", place: "Sydney", id: 64};
    let rec3 = Record { name: "Akshat", place: "Delhi", id: 98};

    wtr.serialize(rec1)?;
    wtr.serialize(rec2)?;
    wtr.serialize(rec3)?;

    wtr.flush()?;

    Ok(())
}

Преобразование CSV столбца

csv-badge serde-badge cat-encoding-badge

Преобразование CSV файла, содержащего имя цвета и шестнадцатеричный цвет в файл с именем цвета и rgb цветом. Используется крейт csv для чтения и записи csv файла и serde для десериализации и сериализации строк в/из байт.

Смотри методы csv::Reader::deserialize, serde::Deserialize и std::str::FromStr

extern crate csv;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;
extern crate serde;

use csv::{Reader, Writer};
use serde::{de, Deserialize, Deserializer};
use std::str::FromStr;

error_chain! {
   foreign_links {
       CsvError(csv::Error);
       ParseInt(std::num::ParseIntError);
       CsvInnerError(csv::IntoInnerError<Writer<Vec<u8>>>);
       IO(std::fmt::Error);
       UTF8(std::string::FromUtf8Error);
   }
}

#[derive(Debug)]
struct HexColor {
    red: u8,
    green: u8,
    blue: u8,
}

#[derive(Debug, Deserialize)]
struct Row {
    color_name: String,
    color: HexColor,
}

impl FromStr for HexColor {
    type Err = Error;

    fn from_str(hex_color: &str) -> std::result::Result<Self, Self::Err> {
        let trimmed = hex_color.trim_matches('#');
        if trimmed.len() != 6 {
            Err("Invalid length of hex string".into())
        } else {
            Ok(HexColor {
                red: u8::from_str_radix(&trimmed[..2], 16)?,
                green: u8::from_str_radix(&trimmed[2..4], 16)?,
                blue: u8::from_str_radix(&trimmed[4..6], 16)?,
            })
        }
    }
}

impl<'de> Deserialize<'de> for HexColor {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        FromStr::from_str(&s).map_err(de::Error::custom)
    }
}

fn main() -> Result<()> {
    let data = "color_name,color
red,#ff0000
green,#00ff00
blue,#0000FF
periwinkle,#ccccff
magenta,#ff00ff"
        .to_owned();
    let mut out = Writer::from_writer(vec![]);
    let mut reader = Reader::from_reader(data.as_bytes());
    for result in reader.deserialize::<Row>() {
        let res = result?;
        out.serialize((
            res.color_name,
            res.color.red,
            res.color.green,
            res.color.blue,
        ))?;
    }
    let written = String::from_utf8(out.into_inner()?)?;
    assert_eq!(Some("magenta,255,0,255"), written.lines().last());
    println!("{}", written);
    Ok(())
}

Структурированные данные

Сериализация и десериализация неструктурированного JSON

serde-json-badge cat-encoding-badge

Крейт serde_json предоставляет функцию from_str для разбора и анализа &str JSON.

Неструктурированный JSON можно проанализировать в универсальное значение serde_json::Value, которое может представлять любые допустимые данные JSON.

В приведённом ниже примере показано, как анализируется &str с JSON. Ожидаемое значение объявляется с помощью макроса json!.

#[macro_use]
extern crate serde_json;

use serde_json::{Value, Error};

fn main() -> Result<(), Error> {
    let j = r#"{
                 "userid": 103609,
                 "verified": true,
                 "access_privileges": [
                   "user",
                   "admin"
                 ]
               }"#;

    let parsed: Value = serde_json::from_str(j)?;

    let expected = json!({
        "userid": 103609,
        "verified": true,
        "access_privileges": [
            "user",
            "admin"
        ]
    });

    assert_eq!(parsed, expected);

    Ok(())
}

Десериализация файла конфигурации TOML

toml-badge cat-encoding-badge

Разбор некоторый TOML в универсальное toml::Value, которое может представлять любые допустимые TOML данные.

extern crate toml;

use toml::{Value, de::Error};

fn main() -> Result<(), Error> {
    let toml_content = r#"
          [package]
          name = "your_package"
          version = "0.1.0"
          authors = ["You! <you@example.org>"]

          [dependencies]
          serde = "1.0"
          "#;

    let package_info: Value = toml::from_str(toml_content)?;

    assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0"));
    assert_eq!(package_info["package"]["name"].as_str(),
               Some("your_package"));

    Ok(())
}

Разбор TOML в ваши собственные структуры, используя Serde.

#[macro_use]
extern crate serde_derive;
extern crate toml;

use toml::de::Error;
use std::collections::HashMap;

#[derive(Deserialize)]
struct Config {
    package: Package,
    dependencies: HashMap<String, String>,
}

#[derive(Deserialize)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
}

fn main() -> Result<(), Error> {
    let toml_content = r#"
          [package]
          name = "your_package"
          version = "0.1.0"
          authors = ["You! <you@example.org>"]

          [dependencies]
          serde = "1.0"
          "#;

    let package_info: Config = toml::from_str(toml_content)?;

    assert_eq!(package_info.package.name, "your_package");
    assert_eq!(package_info.package.version, "0.1.0");
    assert_eq!(package_info.package.authors, vec!["You! <you@example.org>"]);
    assert_eq!(package_info.dependencies["serde"], "1.0");

    Ok(())
}

Чтение и запись целых чисел в прямом порядке байтов

byteorder-badge cat-encoding-badge

Крейт byteorder поможет развернуть значимые байты структурированных данных. Это может быть необходимо при получении информации по сети, в случае если полученные байты поступают из другой системы.

extern crate byteorder;

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::Error;

#[derive(Default, PartialEq, Debug)]
struct Payload {
    kind: u8,
    value: u16,
}

fn main() -> Result<(), Error> {
    let original_payload = Payload::default();
    let encoded_bytes = encode(&original_payload)?;
    let decoded_payload = decode(&encoded_bytes)?;
    assert_eq!(original_payload, decoded_payload);
    Ok(())
}

fn encode(payload: &Payload) -> Result<Vec<u8>, Error> {
    let mut bytes = vec![];
    bytes.write_u8(payload.kind)?;
    bytes.write_u16::<LittleEndian>(payload.value)?;
    Ok(bytes)
}

fn decode(mut bytes: &[u8]) -> Result<Payload, Error> {
    let payload = Payload {
        kind: bytes.read_u8()?,
        value: bytes.read_u16::<LittleEndian>()?,
    };
    Ok(payload)
}

Обработка ошибок

РецептКрейтыКатегории
Корректная обработка ошибок в функции mainerror-chain-badgecat-rust-patterns-badge
Избегание отбрасывания ошибок в преобразованиях между нимиerror-chain-badgecat-rust-patterns-badge
Получение трассировки в сложных сценариях обработки ошибокerror-chain-badgecat-rust-patterns-badge

Обработка ошибок

Правильная обработка ошибки в main функции

error-chain-badge cat-rust-patterns-badge

Обрабатывает ошибку, возникающую при попытке открыть файл, который не существует. Это достигается с помощью библиотеки error-chain, которая заботится об автоматической генерации большого количества стандартного кода, необходимого для обработки ошибок в Rust .

Io(std::io::Error) внутри foreign_links позволяет автоматическое преобразование из std::io::Error в объявленный тип в error_chain!, реализующий типаж Error.

Приведённый ниже рецепт расскажет, как долго работает система, открыв файл Unix /proc/uptime и проанализировав его содержимое, получив первое число. Он возвращает время безотказной работы, если нет ошибки.

Другие рецепты в этой книге скрывают сгенерированный код error-chain, его можно увидеть, развернув код с помощью кнопки ⤢.

#[macro_use]
extern crate error_chain;

use std::fs::File;
use std::io::Read;

error_chain!{
    foreign_links {
        Io(std::io::Error);
        ParseInt(::std::num::ParseIntError);
    }
}

fn read_uptime() -> Result<u64> {
    let mut uptime = String::new();
    File::open("/proc/uptime")?.read_to_string(&mut uptime)?;

    Ok(uptime
        .split('.')
        .next()
        .ok_or("Cannot parse uptime data")?
        .parse()?)
}

fn main() {
    match read_uptime() {
        Ok(uptime) => println!("uptime: {} seconds", uptime),
        Err(err) => eprintln!("error: {}", err),
    };
}

Избегайте отбрасывания ошибок во время их преобразований

error-chain-badge cat-rust-patterns-badge

Крейт error-chain делает сопоставление с различными типами ошибок, возвращаемых функцией, возможным и относительно компактным. ErrorKind определяет тип ошибки.

Используется библиотека reqwest для запроса веб-службы генератора случайных целых чисел. Преобразует строковый ответ в целое число. Стандартная библиотека Rust, библиотека reqwest и веб-сервис могут вернуть ошибки. Хорошо определённые ошибки Rust используют foreign_links. Дополнительный вариант ErrorKind для ошибки веб-службы использует блок errors в error_chain! макросе.

#[macro_use]
extern crate error_chain;
extern crate reqwest;

use std::io::Read;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Reqwest(reqwest::Error);
        ParseIntError(std::num::ParseIntError);
    }

    errors { RandomResponseError(t: String) }
}

fn parse_response(mut response: reqwest::Response) -> Result<u32> {
    let mut body = String::new();
    response.read_to_string(&mut body)?;
    body.pop();
    body.parse::<u32>()
        .chain_err(|| ErrorKind::RandomResponseError(body))
}

fn run() -> Result<()> {
    let url =
        format!("https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain");
    let response = reqwest::get(&url)?;
    let random_value: u32 = parse_response(response)?;

    println!("a random number between 0 and 10: {}", random_value);

    Ok(())
}

fn main() {
    if let Err(error) = run() {
        match *error.kind() {
            ErrorKind::Io(_) => println!("Standard IO error: {:?}", error),
            ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error),
            ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error),
            ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error),
            _ => println!("Other error: {:?}", error),
        }
    }
}

Получение трассировки (backtrace) сложных сценариев ошибок

error-chain-badge cat-rust-patterns-badge

Этот рецепт показывает, как обрабатывать сложный сценарий ошибки, а затем напечатать обратную трассировку. Он полагается на chain_err для расширения стека ошибок путём добавления новых ошибок. Стек ошибок можно развернуть, что обеспечивает лучший контекст для понимания причины возникновения ошибки.

Приведённые ниже рецепты пытаются десериализовать значение 256 в тип u8. Ошибка всплывёт из Serde, затем csv и наконец к коду пользователя.

extern crate csv;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;

use std::fmt;

error_chain! {
    foreign_links {
        Reader(csv::Error);
    }
}

#[derive(Debug, Deserialize)]
struct Rgb {
    red: u8,
    blue: u8,
    green: u8,
}

impl Rgb {
    fn from_reader(csv_data: &[u8]) -> Result<Rgb> {
        let color: Rgb = csv::Reader::from_reader(csv_data)
            .deserialize()
            .nth(0)
            .ok_or("Cannot deserialize the first CSV record")?
            .chain_err(|| "Cannot deserialize RGB color")?;

        Ok(color)
    }
}

impl fmt::UpperHex for Rgb {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let hexa = u32::from(self.red) << 16 | u32::from(self.blue) << 8 | u32::from(self.green);
        write!(f, "{:X}", hexa)
    }
}

fn run() -> Result<()> {
    let csv = "red,blue,green
102,256,204";

    let rgb = Rgb::from_reader(csv.as_bytes()).chain_err(|| "Cannot read CSV data")?;
    println!("{:?} to hexadecimal #{:X}", rgb, rgb);

    Ok(())
}

fn main() {
    if let Err(ref errors) = run() {
        eprintln!("Error level - description");
        errors
            .iter()
            .enumerate()
            .for_each(|(index, error)| eprintln!("└> {} - {}", index, error));

        if let Some(backtrace) = errors.backtrace() {
            eprintln!("{:?}", backtrace);
        }

        // In a real use case, errors should handled. For example:
        // ::std::process::exit(1);
    }
}

Отображение трассировки ошибки:

Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type

Запустите рецепт с RUST_BACKTRACE=1 чтобы отобразить подробную трассировку backtrace связанную с этой ошибкой.

Файловая система

РецептКрейтыКатегории
Чтение файла построчноstd-badgecat-filesystem-badge
Избегание записи и чтения из одного и того же файлаsame_file-badgecat-filesystem-badge
Произвольный доступ к файлу с использованием проекции в память (memory map)memmap-badgecat-filesystem-badge
Получение файлов, которые были модифицированы за последние 24 часаstd-badgecat-filesystem-badge cat-os-badge
Нахождение циклов в заданном путиsame_file-badgecat-filesystem-badge
Рекурсивное нахождение одинаковых имён файловwalkdir-badgecat-filesystem-badge
Рекурсивное нахождение всех файлов, удовлетворяющим заданному предикатуwalkdir-badgecat-filesystem-badge
Обход каталога с пропуском файлов, начинающихся с точки (dotfiles)walkdir-badgecat-filesystem-badge
Рекурсивное вычисление размеров файлов по заданному путиwalkdir-badgecat-filesystem-badge
Рекурсивное нахождение всех png-файловglob-badgecat-filesystem-badge
Нахождение всех файлов по заданному паттерну без учёта регистра символовglob-badgecat-filesystem-badge

Чтение и запись

Чтение строк из файла

std-badge cat-filesystem-badge

Записывает в файл сообщение из трёх строк, а затем читает его обратно по строчке за раз с помощью итератора Lines, созданного с помощью BufRead::lines. File реализует Read, который предоставляет типаж BufReader. Метод File::create открывает File для записи, а File::open для чтения.

use std::fs::File;
use std::io::{Write, BufReader, BufRead, Error};

fn main() -> Result<(), Error> {
    let path = "lines.txt";

    let mut output = File::create(path)?;
    write!(output, "Rust\n💖\nFun")?;

    let input = File::open(path)?;
    let buffered = BufReader::new(input);

    for line in buffered.lines() {
        println!("{}", line?);
    }

    Ok(())
}

Избегайте запись и чтение одного файла

same_file-badge cat-filesystem-badge

Используйте same_file::Handle для файла, который можно проверить на равенство с другими дескрипторами. В этом примере дескрипторы файла для чтения и записи проверяются на равенство.

extern crate same_file;

use same_file::Handle;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::path::Path;

fn main() -> Result<(), Error> {
    let path_to_read = Path::new("new.txt");

    let stdout_handle = Handle::stdout()?;
    let handle = Handle::from_path(path_to_read)?;

    if stdout_handle == handle {
        return Err(Error::new(
            ErrorKind::Other,
            "You are reading and writing to the same file",
        ));
    } else {
        let file = File::open(&path_to_read)?;
        let file = BufReader::new(file);
        for (num, line) in file.lines().enumerate() {
            println!("{} : {}", num, line?.to_uppercase());
        }
    }

    Ok(())
}
cargo run

отображает содержимое файла new.txt.

cargo run >> ./new.txt

ошибки, потому что два файла одинаковы.

Произвольный доступ к файлу с помощью карты памяти

memmap-badge cat-filesystem-badge

Создаёт карту памяти файла с использованием крейта memmap и моделирует некоторые непоследовательные чтения из файла. Использование карты памяти означает, что вы просто индексируете срезы, а не делаете с помощью seek навигацию по файлу.

Функция Mmap::map предполагает, что файл индексированный картой памяти не изменяется одновременно с другим процессом, в противном случае возникает состояние гонки .

extern crate memmap;

use memmap::Mmap;
use std::fs::File;
use std::io::{Write, Error};

fn main() -> Result<(), Error> {
    write!(File::create("content.txt")?, "My hovercraft is full of eels!")?;

    let file = File::open("content.txt")?;
    let map = unsafe { Mmap::map(&file)? };

    let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29];
    assert_eq!(&map[3..13], b"hovercraft");
    let random_bytes: Vec<u8> = random_indexes.iter()
        .map(|&idx| map[idx])
        .collect();
    assert_eq!(&random_bytes[..], b"My loaf!");
    Ok(())
}

Обход каталогов

Имена файлов, которые были изменены за последние 24 часа

std-badge cat-filesystem-badge

Получение текущего рабочего каталога, вызывая env::current_dir, затем для каждой записи в fs::read_dir извлекаем DirEntry::path и получаем метаданные с помощью fs::Metadata. Метод Metadata::modified возвращает время SystemTime::elapsed с момента последней модификации. Метод Duration::as_secs преобразует время в секунды и сравнивается с 24 часами (24 * 60 * 60 секунд). Метод Metadata::is_file отфильтровывает каталоги.

#[macro_use]
extern crate error_chain;

use std::{env, fs};

error_chain! {
    foreign_links {
        Io(std::io::Error);
        SystemTimeError(std::time::SystemTimeError);
    }
}

fn main() -> Result<()> {
    let current_dir = env::current_dir()?;
    println!(
        "Entries modified in the last 24 hours in {:?}:",
        current_dir
    );

    for entry in fs::read_dir(current_dir)? {
        let entry = entry?;
        let path = entry.path();

        let metadata = fs::metadata(&path)?;
        let last_modified = metadata.modified()?.elapsed()?.as_secs();

        if last_modified < 24 * 3600 && metadata.is_file() {
            println!(
                "Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
                last_modified,
                metadata.permissions().readonly(),
                metadata.len(),
                path.file_name().ok_or("No filename")?
            );
        }
    }

    Ok(())
}

Найти зацикливания у заданного пути

same_file-badge cat-filesystem-badge

Используется same_file::is_same_file для обнаружения зацикливания по заданному пути. Например, цикл может быть создан в системе Unix через символические ссылки:

mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/  /tmp/foo/bar/baz/qux

Следующей код будет утверждать, что цикл существует.

extern crate same_file;

use std::io;
use std::path::{Path, PathBuf};
use same_file::is_same_file;

fn contains_loop<P: AsRef<Path>>(path: P) -> io::Result<Option<(PathBuf, PathBuf)>> {
    let path = path.as_ref();
    let mut path_buf = path.to_path_buf();
    while path_buf.pop() {
        if is_same_file(&path_buf, path)? {
            return Ok(Some((path_buf, path.to_path_buf())));
        } else if let Some(looped_paths) = contains_loop(&path_buf)? {
            return Ok(Some(looped_paths));
        }
    }
    return Ok(None);
}

fn main() {
    assert_eq!(
        contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(),
        Some((
            PathBuf::from("/tmp/foo"),
            PathBuf::from("/tmp/foo/bar/baz/qux")
        ))
    );
}

Рекурсивно найти повторяющиеся имена файлов

walkdir-badge cat-filesystem-badge

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

extern crate walkdir;

use std::collections::HashMap;
use walkdir::WalkDir;

fn main() {
    let mut filenames = HashMap::new();

    for entry in WalkDir::new(".")
            .into_iter()
            .filter_map(Result::ok)
            .filter(|e| !e.file_type().is_dir()) {
        let f_name = String::from(entry.file_name().to_string_lossy());
        let counter = filenames.entry(f_name.clone()).or_insert(0);
        *counter += 1;

        if *counter == 2 {
            println!("{}", f_name);
        }
    }
}

Рекурсивно найти все файлы с заданным предикатом

walkdir-badge cat-filesystem-badge

Найти JSON файлы, изменённые за последний день в текущем каталоге. Использование метода follow_links гарантирует, что символические ссылки будут пройдены, как если бы они были обычными каталогами и файлами.

#[macro_use]
extern crate error_chain;
extern crate walkdir;

use walkdir::WalkDir;

error_chain! {
    foreign_links {
        WalkDir(walkdir::Error);
        Io(std::io::Error);
        SystemTime(std::time::SystemTimeError);
    }
}

fn main() -> Result<()> {
    for entry in WalkDir::new(".")
            .follow_links(true)
            .into_iter()
            .filter_map(|e| e.ok()) {
        let f_name = entry.file_name().to_string_lossy();
        let sec = entry.metadata()?.modified()?;

        if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 {
            println!("{}", f_name);
        }
    }

    Ok(())
}

Обход каталогов с пропуском файлов с точкой в начале имени

walkdir-badge cat-filesystem-badge

Используется filter_entry для рекурсивного обхода записей, передавая предикат is_not_hidden для пропуска скрытых файлов и каталогов. Метод Iterator::filter применяется к каждому WalkDir::DirEntry, даже если родительский объект является скрытым каталогом.

Корневой каталог "." передаётся в использование методом WalkDir::depth в предикате is_not_hidden .

extern crate walkdir;

use walkdir::{DirEntry, WalkDir};

fn is_not_hidden(entry: &DirEntry) -> bool {
    entry
         .file_name()
         .to_str()
         .map(|s| entry.depth() == 0 || !s.starts_with("."))
         .unwrap_or(false)
}

fn main() {
    WalkDir::new(".")
        .into_iter()
        .filter_entry(|e| is_not_hidden(e))
        .filter_map(|v| v.ok())
        .for_each(|x| println!("{}", x.path().display()));
}

Рекурсивно рассчитать размеры файлов на заданной глубине

walkdir-badge cat-filesystem-badge

Глубина рекурсии может быть гибко установлена методами WalkDir::min_depth и WalkDir::max_depth. Вычисление суммы всех размеров файлов до глубины под папок в 3 уровня, игнорируя файлы в корневом каталоге.

extern crate walkdir;

use walkdir::WalkDir;

fn main() {
    let total_size = WalkDir::new(".")
        .min_depth(1)
        .max_depth(3)
        .into_iter()
        .filter_map(|entry| entry.ok())
        .filter_map(|entry| entry.metadata().ok())
        .filter(|metadata| metadata.is_file())
        .fold(0, |acc, m| acc + m.len());

    println!("Total size: {} bytes.", total_size);
}

Найти рекурсивно все png файлы

glob-badge cat-filesystem-badge

В этом примере выполняется задача рекурсивно найти все файлы PNG в текущем каталоге. В данном случае шаблон ** соответствует текущему каталогу и всем его подкаталогам.

Шаблон ** используется для любой части пути. Например, /media/**/*.png соответствует всем PNG в каталоге media и его подкаталогах.

#[macro_use]
extern crate error_chain;
extern crate glob;

use glob::glob;

error_chain! {
    foreign_links {
        Glob(glob::GlobError);
        Pattern(glob::PatternError);
    }
}

fn main() -> Result<()> {
    for entry in glob("**/*.png")? {
        println!("{}", entry?.display());
    }

    Ok(())
}

Найти все файлы с заданным шаблоном, игнорируя регистр в имени файла

glob-badge cat-filesystem-badge

Найдите все файлы изображений в каталоге /media/ соответствующие образцу img_[0-9]*.png.

Пользовательская структура MatchOptions передаётся в функцию glob_with делая шаблон glob нечувствительным к регистру, оставляя другие параметры по умолчанию как Default.

#[macro_use]
extern crate error_chain;
extern crate glob;

use glob::{glob_with, MatchOptions};

error_chain! {
    foreign_links {
        Glob(glob::GlobError);
        Pattern(glob::PatternError);
    }
}

fn main() -> Result<()> {
    let options = MatchOptions {
        case_sensitive: false,
        ..Default::default()
    };

    for entry in glob_with("/media/img_[0-9]*.png", &options)? {
        println!("{}", entry?.display());
    }

    Ok(())
}

Аппаратный доступ

РецептКрейтыКатегории
Проверка количества ядер процессораПроверка количества ядер процессораcat-hardware-support-badge

Процессор

Проверка количество логических ядер процессора

num_cpus-badge cat-hardware-support-badge

Показывает количество логических ядер процессора на текущей машине, используя [ num_cpus::get ].

extern crate num_cpus;

fn main() {
    println!("Number of logical cores is {}", num_cpus::get());
}

Управление памятью

РецептКрейтыКатегории
Определение лениво вычисляемой константыlazy_static-badgecat-caching-badge cat-rust-patterns-badge

Константы

Объявление лениво вычисляемой константы

lazy_static-badge cat-caching-badge cat-rust-patterns-badge

Объявляет лениво вычисляемую константу типа HashMap. Коллекция HashMap будет сразу вычислена и сохранена в глобальной статической ссылке.

#[macro_use]
extern crate lazy_static;

use std::collections::HashMap;

lazy_static! {
    static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
        let mut map = HashMap::new();
        map.insert("James", vec!["user", "admin"]);
        map.insert("Jim", vec!["user"]);
        map
    };
}

fn show_access(name: &str) {
    let access = PRIVILEGES.get(name);
    println!("{}: {:?}", name, access);
}

fn main() {
    let access = PRIVILEGES.get("James");
    println!("James: {:?}", access);

    show_access("Jim");
}

Работа с сетью

РецептКрейтыКатегории
Прослушивание неиспользуемого TCP/IP портаstd-badgecat-net-badge

Сервер

Прослушивание неиспользуемого порта TCP/IP

std-badge cat-net-badge

В этом примере порт отображается в консоли и программа будет прослушивать, пока на него не будет сделан запрос. SocketAddrV4 назначает случайный порт при установке порта в 0.

use std::net::{SocketAddrV4, Ipv4Addr, TcpListener};
use std::io::{Read, Error};

fn main() -> Result<(), Error> {
    let loopback = Ipv4Addr::new(127, 0, 0, 1);
    let socket = SocketAddrV4::new(loopback, 0);
    let listener = TcpListener::bind(socket)?;
    let port = listener.local_addr()?;
    println!("Listening on {}, access this port to end the program", port);
    let (mut tcp_stream, addr) = listener.accept()?; //block  until requested
    println!("Connection received! {:?} is sending data.", addr);
    let mut input = String::new();
    let _ = tcp_stream.read_to_string(&mut input)?;
    println!("{:?} says {}", addr, input);
    Ok(())
}

Операционная система

РецептКрейтыКатегории
Запуск внешней команды и обработка её выводаregex-badgecat-os-badge cat-text-processing-badge
Запуск внешней команды, передача ей стандартного ввода и проверка кода ошибкиregex-badgecat-os-badge cat-text-processing-badge
Запуск конвейера внешних командstd-badgecat-os-badge
Перенаправление вывода stdout и stderr дочернего процесса в один и тот же файлstd-badgecat-os-badge
Непрерывная обработка вывода нескольких дочерних процессовstd-badgecat-os-badgecat-text-processing-badge

Внешняя команда

Запуск внешне команды и обработка стандартного вывода

regex-badge cat-os-badge cat-text-processing-badge

Запускает git log --oneline как внешнюю команду Command и проверяет её вывод Output используя Regex, чтобы получить хеш и сообщение о последних 5 коммитах.

#[macro_use]
extern crate error_chain;
extern crate regex;

use std::process::Command;
use regex::Regex;

error_chain!{
    foreign_links {
        Io(std::io::Error);
        Regex(regex::Error);
        Utf8(std::string::FromUtf8Error);
    }
}

#[derive(PartialEq, Default, Clone, Debug)]
struct Commit {
    hash: String,
    message: String,
}

fn main() -> Result<()> {
    let output = Command::new("git").arg("log").arg("--oneline").output()?;

    if !output.status.success() {
        bail!("Command executed with failing error code");
    }

    let pattern = Regex::new(r"(?x)
                               ([0-9a-fA-F]+) # commit hash
                               (.*)           # The commit message")?;

    String::from_utf8(output.stdout)?
        .lines()
        .filter_map(|line| pattern.captures(line))
        .map(|cap| {
                 Commit {
                     hash: cap[1].to_string(),
                     message: cap[2].trim().to_string(),
                 }
             })
        .take(5)
        .for_each(|x| println!("{:?}", x));

    Ok(())
}

Запуск внешней команды, передав ей stdin, и проверка кода ошибки

std-badge cat-os-badge

Открывает интерпретатор python с помощью внешней команды Command и передаёт ему инструкции языка python для выполнения. Вывод Output оператора затем анализируется.

#[macro_use]
extern crate error_chain;

use std::collections::HashSet;
use std::io::Write;
use std::process::{Command, Stdio};

error_chain!{
    errors { CmdError }
    foreign_links {
        Io(std::io::Error);
        Utf8(std::string::FromUtf8Error);
    }
}

fn main() -> Result<()> {
    let mut child = Command::new("python").stdin(Stdio::piped())
        .stderr(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;

    child.stdin
        .as_mut()
        .ok_or("Child process stdin has not been captured!")?
        .write_all(b"import this; copyright(); credits(); exit()")?;

    let output = child.wait_with_output()?;

    if output.status.success() {
        let raw_output = String::from_utf8(output.stdout)?;
        let words = raw_output.split_whitespace()
            .map(|s| s.to_lowercase())
            .collect::<HashSet<_>>();
        println!("Found {} unique words:", words.len());
        println!("{:#?}", words);
        Ok(())
    } else {
        let err = String::from_utf8(output.stderr)?;
        bail!("External command failed:\n {}", err)
    }
}

Запуск внешние команды цепочками

std-badge cat-os-badge

Показывает до десяти самых больших файлов и подкаталогов в текущем рабочем каталоге. Это эквивалентно запуску: du -ah . | sort -hr | head -n 10.

Command-ы представляют процесс. Вывод дочернего процесса захватывается с помощью Stdio::piped между родительским и дочерним процессами.

#[macro_use]
extern crate error_chain;

use std::process::{Command, Stdio};

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Utf8(std::string::FromUtf8Error);
    }
}

fn main() -> Result<()> {
    let directory = std::env::current_dir()?;
    let mut du_output_child = Command::new("du")
        .arg("-ah")
        .arg(&directory)
        .stdout(Stdio::piped())
        .spawn()?;

    if let Some(du_output) = du_output_child.stdout.take() {
        let mut sort_output_child = Command::new("sort")
            .arg("-hr")
            .stdin(du_output)
            .stdout(Stdio::piped())
            .spawn()?;

        du_output_child.wait()?;

        if let Some(sort_output) = sort_output_child.stdout.take() {
            let head_output_child = Command::new("head")
                .args(&["-n", "10"])
                .stdin(sort_output)
                .stdout(Stdio::piped())
                .spawn()?;

            let head_stdout = head_output_child.wait_with_output()?;

            sort_output_child.wait()?;

            println!(
                "Top 10 biggest files and directories in '{}':\n{}",
                directory.display(),
                String::from_utf8(head_stdout.stdout).unwrap()
            );
        }
    }

    Ok(())
}

Перенаправить stdout и stderr дочернего процесса в один файл

std-badge cat-os-badge

Создаёт дочерний процесс и перенаправляет stdout и stderr в один и тот же файл. Рецепт следует той же идее, что и запуск внешних команд цепочкой , однако process::Stdio записывает в указанный файл. File::try_clone ссылается на один и тот же дескриптор файла для stdout и stderr. Это гарантирует, что оба дескриптора пишут с одинаковой позиции курсора.

Приведённый ниже рецепт эквивалентен запуску команды оболочки Unix ls . oops >out.txt 2>&1.

use std::fs::File;
use std::io::Error;
use std::process::{Command, Stdio};

fn main() -> Result<(), Error> {
    let outputs = File::create("out.txt")?;
    let errors = outputs.try_clone()?;

    Command::new("ls")
        .args(&[".", "oops"])
        .stdout(Stdio::from(outputs))
        .stderr(Stdio::from(errors))
        .spawn()?
        .wait_with_output()?;

    Ok(())
}

Постоянная обработка вывода дочернего процесса

std-badge cat-os-badge

В разделе Выполнить внешнюю команду и обработать её стандартный вывод обработка не начинается до тех пор, пока не завершится внешняя Command. Приведённый ниже рецепт вызывает Stdio::piped для создания канала и непрерывно читает стандартный stdout как только обновляется BufReader .

Этот рецепт эквивалентен команде оболочки Unix journalctl | grep usb.

use std::process::{Command, Stdio};
use std::io::{BufRead, BufReader, Error, ErrorKind};

fn main() -> Result<(), Error> {
    let stdout = Command::new("journalctl")
        .stdout(Stdio::piped())
        .spawn()?
        .stdout
        .ok_or_else(|| Error::new(ErrorKind::Other,"Could not capture standard output."))?;

    let reader = BufReader::new(stdout);

    reader
        .lines()
        .filter_map(|line| line.ok())
        .filter(|line| line.find("usb").is_some())
        .for_each(|line| println!("{}", line));

     Ok(())
}

Наука

Математика

РецептКрейтыКатегории
Сумма вектораndarray-badgecat-science-badge
Норма вектораndarray-badgecat-science-badge
Сложение матрицndarray-badgecat-science-badge
Перемножение матрицndarray-badgecat-science-badge
Умножение скаляра на вектор и матрицуndarray-badgecat-science-badge
Инвертирование матрицыnalgebra-badgecat-science-badge
Расчёт боковой стороны треугольникаstd-badgecat-science-badge
Проверка, что тангенс равен синусу делённому на косинусstd-badgecat-science-badge
Расстояние между двумя точками на поверхности сферыstd-badgecat-science-badge
Создание комплексных чиселnum-badgecat-science-badge
Сложение комплексных числеnum-badgecat-science-badge
Математические функции комплексных чиселnum-badgecat-science-badge
Измерение среднего значенияstd-badgecat-science-badge
Вычисление стандартного отклоненияstd-badgecat-science-badge
Большие целые числаnum-badgecat-science-badge

Математика

РецептКрейтыКатегории
Сумма вектораndarray-badgecat-science-badge
Норма вектораndarray-badgecat-science-badge
Сложение матрицndarray-badgecat-science-badge
Перемножение матрицndarray-badgecat-science-badge
Умножение скаляра на вектор и матрицуndarray-badgecat-science-badge
Инвертирование матрицыnalgebra-badgecat-science-badge
Расчёт боковой стороны треугольникаstd-badgecat-science-badge
Проверка, что тангенс равен синусу делённому на косинусstd-badgecat-science-badge
Расстояние между двумя точками на поверхности сферыstd-badgecat-science-badge
Создание комплексных чиселnum-badgecat-science-badge
Сложение комплексных числеnum-badgecat-science-badge
Математические функции комплексных чиселnum-badgecat-science-badge
Измерение среднего значенияstd-badgecat-science-badge
Вычисление стандартного отклоненияstd-badgecat-science-badge
Большие целые числаnum-badgecat-science-badge

Линейная алгебра

Сложение матриц

ndarray-badge cat-science-badge

Создаёт две двумерные матрицы (2-D) с помощью ndarray::arr2 и суммирует их поэлементно.

Обратите внимание, что сумма вычисляется как let sum = &a + &b. Оператор & используется, чтобы избежать поглощения a и b и сделать их доступными для отображения позже. Создаётся новый массив, содержащий их сумму.

extern crate ndarray;

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3],
                   [4, 5, 6]]);

    let b = arr2(&[[6, 5, 4],
                   [3, 2, 1]]);

    let sum = &a + &b;

    println!("{}", a);
    println!("+");
    println!("{}", b);
    println!("=");
    println!("{}", sum);
}

Перемножение матриц

ndarray-badge cat-science-badge

Создаёт две матрицы с помощью ndarray::arr2 и выполняет перемножение матриц с помощью ndarray::ArrayBase::dot.

extern crate ndarray;

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3],
                   [4, 5, 6]]);
    let b = arr2(&[[6, 3],
                   [5, 2],
                   [4, 1]]);

    println!("{}", a.dot(&b));
}

Умножение скаляра на вектор и на матрицу

ndarray-badge cat-science-badge

Создаёт одномерный 1-D массив (вектор) с помощью ndarray::arr1 и двумерных 2-D массив (матрицу) с помощью ndarray::arr2.

Сначала скаляр умножается на вектор, чтобы получить другой вектор. Затем матрица умножается на новый вектор с помощью ndarray::Array2::dot. (Матричное умножение выполняется с использованием метода dot, а оператор * выполняет поэлементное умножение.)

В ndarray массивы можно интерпретировать как либо векторы строк, либо столбцов, в зависимости от контекста. Если важно представить ориентацию вектора, необходимо использовать двумерный массив с одной строкой или одним столбцом. В этом примере вектор является 1-D массивом справа, поэтому dot обрабатывает его как вектор столбца.

extern crate ndarray;
use ndarray::{arr1, arr2, Array1};
fn main() {
    let scalar = 4;
    let vector = arr1(&[1, 2, 3]);
    let matrix = arr2(&[[4, 5, 6],
                        [7, 8, 9]]);
    let new_vector: Array1<_> = scalar * vector;
    println!("{}", new_vector);
    let new_matrix = matrix.dot(&new_vector);
    println!("{}", new_matrix);
}

Сравнение вектора

ndarray-badge

Крейт ndarray поддерживает несколько способов создания массивов - этот рецепт создаёт ndarray::Array массивы из std::Vec, используя from. Затем он суммирует массивы поэлементно.

Этот рецепт содержит пример поэлементного сравнения двух векторов с плавающей точкой. Числа с плавающей точкой часто хранятся неточно, что затрудняет точное сравнение. Тем не менее, assert_abs_diff_eq! макрос из крейта approx позволяет удобное, поэлементное сравнение. Для того, чтобы использовать approx крейта с ndarray, то функция approx должна быть добавлена к ndarray зависимости в Cargo.toml. Например, ndarray = { version = "0.13", features = ["approx"] } .

Этот рецепт также содержит дополнительные примеры владения. Здесь let z = a + b потребляет a и b, обновляет переменную a результатом, а затем переносит владение в z. В качестве альтернативы, let w = &c + &d создает новый вектор без использования c или d, что позволяет их модифицировать позже. См. Бинарные операторы с двумя массивами для дополнительной информации.

#[macro_use(assert_abs_diff_eq)]
extern crate approx;
extern crate ndarray;

use ndarray::Array;

fn main() {
  let a = Array::from(vec![1., 2., 3., 4., 5.]);
  let b = Array::from(vec![5., 4., 3., 2., 1.]);
  let mut c = Array::from(vec![1., 2., 3., 4., 5.]);
  let mut d = Array::from(vec![5., 4., 3., 2., 1.]);
  let z = a + b;
  let w =  &c + &d;
  assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));
  println!("c = {}", c);
  c[0] = 10.;
  d[1] = 10.;
  assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
}

Норма вектора

ndarray-badge

Этот рецепт демонстрирует использование типа Array1, типа ArrayView1, метода fold, метода dot при вычислении нормы l1 и [l2] для данного вектора.

  • Функция l2_norm является более простой из двух, так как она вычисляет квадратный корень из точечного произведения вектора с самим собой.
  • Функция l1_norm вычисляется с помощью операции fold, которая суммирует абсолютные значения элементов. (Это также может быть выполнено с помощью x.mapv(f64::abs).scalar_sum(), но это выделит новый массив для результата mapv.)

Обратите внимание, что и l1_norm и l2_norm принимают аргумент типа ArrayView1. Этот рецепт учитывает векторные нормы, поэтому функции норм должны принимать только одномерные представления (следовательно, ArrayView1 ). Хотя функции могут принимать параметр типа &Array1<f64>, это потребует от вызывающей стороны иметь ссылку на собственный массив, что является более ограничительным, чем просто доступ к представлению (поскольку представление может быть создано из любого массива или просмотр, а не просто принадлежащий массив).

Типы Array и ArrayView являются псевдонимами для типов ArrayBase. Таким образом, наиболее общий тип аргумента для вызывающей стороны будет &ArrayBase<S, Ix1> where S: Data, потому что когда вызывающая сторона может использовать &array или & view вместо x.view(). Если функция является частью публичного API, это может быть лучшим выбором для удобства пользователей. Для внутренних функций предпочтительным может быть более лаконичный ArrayView1<f64>.

#[macro_use(array)]
extern crate ndarray;

use ndarray::{Array1, ArrayView1};

fn l1_norm(x: ArrayView1<f64>) -> f64 {
    x.fold(0., |acc, elem| acc + elem.abs())
}

fn l2_norm(x: ArrayView1<f64>) -> f64 {
    x.dot(&x).sqrt()
}

fn normalize(mut x: Array1<f64>) -> Array1<f64> {
    let norm = l2_norm(x.view());
    x.mapv_inplace(|e| e/norm);
    x
}

fn main() {
    let x = array![1., 2., 3., 4., 5.];
    println!("||x||_2 = {}", l2_norm(x.view()));
    println!("||x||_1 = {}", l1_norm(x.view()));
    println!("Normalizing x yields {:?}", normalize(x));
}

[l2]: https://docs.rs/ndarray/*/ndarray/type.ArrayView1.html ## Инвертирование матрицы

nalgebra-badge cat-science-badge

Создаёт матрицу 3x3 с помощью nalgebra::Matrix3 и инвертирует её, если возможно.

extern crate nalgebra;

use nalgebra::Matrix3;

fn main() {
    let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
    println!("m1 = {}", m1);
    match m1.try_inverse() {
        Some(inv) => {
            println!("The inverse of m1 is: {}", inv);
        }
        None => {
            println!("m1 is not invertible!");
        }
    }
}

Тригонометрия

Расчёт длины стороны треугольника

std-badge cat-science-badge

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

fn main() {
    let angle: f64 = 2.0;
    let side_length = 80.0;

    let hypotenuse = side_length / angle.sin();

    println!("Hypotenuse: {}", hypotenuse);
}
``` ## Проверка того, что тангенс равен синусу, разделённому на косинус

[![std-badge]][std] [![cat-science-badge]][cat-science]

Проверяет, что tan (x) равен sin (x) / cos (x) для x = 6.

```rust
fn main() {
    let x: f64 = 6.0;

    let a = x.tan();
    let b = x.sin() / x.cos();

    assert_eq!(a, b);
}
``` ## Расстояние между двумя точками на земной сфере

[![std-badge]][std]

По умолчанию Rust предоставляет математические [методы с плавающей точкой,] такие как тригонометрические функции, квадратный корень, функции преобразования между радианами и градусами и т.д.

В следующем примере вычисляется расстояние в километрах между двумя точками на Земле с помощью [формулы Хаверсина]. Точки задаются парами значений широты и долготы в градусах. Затем метод [`to_radians`] преобразует их в радианы. Метод [`sin`], [`cos`], [`powi`] и [`sqrt`] вычисляют центральный угол. Наконец, можно рассчитать расстояние.

```rust
fn main() {
    let earth_radius_kilometer = 6371.0_f64;
    let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, -2.34880_f64);
    let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);

    let paris_latitude = paris_latitude_degrees.to_radians();
    let london_latitude = london_latitude_degrees.to_radians();

    let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians();
    let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians();

    let central_angle_inner = (delta_latitude / 2.0).sin().powi(2)
        + paris_latitude.cos() * london_latitude.cos() * (delta_longitude / 2.0).sin().powi(2);
    let central_angle = 2.0 * central_angle_inner.sqrt().asin();

    let distance = earth_radius_kilometer * central_angle;

    println!(
        "Distance between Paris and London on the surface of Earth is {:.1} kilometers",
        distance
    );
}

Комплексные числа

Создание комплексных чисел

num-badge cat-science-badge

Создаёт комплексные числа типа num::complex::Complex. И действительная, и мнимая части комплексного числа должны быть одного типа.

extern crate num;

fn main() {
    let complex_integer = num::complex::Complex::new(10, 20);
    let complex_float = num::complex::Complex::new(10.1, 20.1);

    println!("Complex integer: {}", complex_integer);
    println!("Complex float: {}", complex_float);
}

num::complex::Complex: https://autumnai.github.io/cuticula/num/complex/struct.Complex.html ## Сложение комплексных чисел

num-badge cat-science-badge

Выполнение математических операций над комплексными числами является таким же как и для встроенных типов: рассматриваемые числа должны быть одного типа (то есть с плавающей точкой или целыми числами).

extern crate num;

fn main() {
    let complex_num1 = num::complex::Complex::new(10.0, 20.0); // Must use floats
    let complex_num2 = num::complex::Complex::new(3.1, -4.2);

    let sum = complex_num1 + complex_num2;

    println!("Sum: {}", sum);
}
``` ## Математические функции

[![num-badge]][num] [![cat-science-badge]][cat-science]

Комплексные числа обладают рядом интересных свойств, когда речь заходит о том как они взаимодействуют с другими математическими функциями, в частности с семейством синусоидальных функций, а также с числом e. Чтобы использовать эти функции с комплексными числами, у типа Complex есть несколько встроенных функций, все из которых можно найти здесь: [`num::complex::Complex`].

```rust
extern crate num;

use std::f64::consts::PI;
use num::complex::Complex;

fn main() {
    let x = Complex::new(0.0, 2.0*PI);

    println!("e^(2i * pi) = {}", x.exp()); // =~1
}

Статистика

Меры среднего значения

std-badge cat-science-badge

В этих примерах вычисляются показатели среднего значения для набора данных, содержащегося в Rust массиве. Для пустого набора данных не может быть среднего значения, медианы или моды, поэтому каждая функция возвращает [ Option ], который должен быть обработан вызывающей стороной.

В первом примере вычисляется среднее значение (сумма всех измерений, делённая на количество измерений в наборе) путём создания итератора ссылок по данным и использования [sum] и [len] для определения соответственно общего значения и количества значений.

fn main() {
    let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];

    let sum = data.iter().sum::<i32>() as f32;
    let count = data.len();

    let mean = match count {
       positive if positive > 0 => Some(sum  / count as f32),
       _ => None
    };

    println!("Mean of the data is {:?}", mean);
}

Во втором примере вычисляется медиана с использованием алгоритма быстрого выбора, который позволяет избежать полного [sort] путём сортировки только тех разделов набора данных, о которых известно, что они могут содержать медиану. При этом используются [cmp] и [Ordering] для краткого выбора следующего исследуемого раздела и [split_at] для выбора произвольного центра в следующем разделе на каждом шаге.

use std::cmp::Ordering;

fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> {
    match data.len() {
        0 => None,
        _ => {
            let (pivot_slice, tail) = data.split_at(1);
            let pivot = pivot_slice[0];
            let (left, right) = tail.iter()
                .fold((vec![], vec![]), |mut splits, next| {
                    {
                        let (ref mut left, ref mut right) = &mut splits;
                        if next < &pivot {
                            left.push(*next);
                        } else {
                            right.push(*next);
                        }
                    }
                    splits
                });

            Some((left, pivot, right))
        }
    }
}

fn select(data: &[i32], k: usize) -> Option<i32> {
    let part = partition(data);

    match part {
        None => None,
        Some((left, pivot, right)) => {
            let pivot_idx = left.len();

            match pivot_idx.cmp(&k) {
                Ordering::Equal => Some(pivot),
                Ordering::Greater => select(&left, k),
                Ordering::Less => select(&right, k - (pivot_idx + 1)),
            }
        },
    }
}

fn median(data: &[i32]) -> Option<f32> {
    let size = data.len();

    match size {
        even if even % 2 == 0 => {
            let fst_med = select(data, (even / 2) - 1);
            let snd_med = select(data, even / 2);

            match (fst_med, snd_med) {
                (Some(fst), Some(snd)) => Some((fst + snd) as f32 / 2.0),
                _ => None
            }
        },
        odd => select(data, odd / 2).map(|x| x as f32)
    }
}

fn main() {
    let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];

    let part = partition(&data);
    println!("Partition is {:?}", part);

    let sel = select(&data, 5);
    println!("Selection at ordered index {} is {:?}", 5, sel);

    let med = median(&data);
    println!("Median is {:?}", med);
}

В последнем примере вычисляется мода с использованием изменяемого [HashMap] для сбора счётчиков каждого отдельного целого числа из набора с использованием API [fold] и [entry]. Наиболее частое значение в [HashMap] всплывает методом [max_by_key].

use std::collections::HashMap;

fn main() {
    let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];

    let frequencies = data.iter().fold(HashMap::new(), |mut freqs, value| {
        *freqs.entry(value).or_insert(0) += 1;
        freqs
    });

    let mode = frequencies
        .into_iter()
        .max_by_key(|&(_, count)| count)
        .map(|(value, _)| *value);

    println!("Mode of the data is {:?}", mode);
}
``` ### Среднеквадратичное отклонение

[![std-badge]][std] [![cat-science-badge]][cat-science]

В этом примере вычисляется стандартное отклонение и z-оценка набора измерений.

Стандартное отклонение определяется как квадратный корень из дисперсии (здесь рассчитывается с помощью [`sqrt`] для f32, где дисперсия представляет собой [`sum`] квадрата разности между каждым измерением и [`mean`], разделённое на количество измерений.

Z-оценка - это число стандартных отклонений, на которое одно измерение отклоняется от [`mean`] набора данных.

```rust
fn mean(data: &[i32]) -> Option<f32> {
    let sum = data.iter().sum::<i32>() as f32;
    let count = data.len();

    match count {
        positive if positive > 0 => Some(sum / count as f32),
        _ => None,
    }
}

fn std_deviation(data: &[i32]) -> Option<f32> {
    match (mean(data), data.len()) {
        (Some(data_mean), count) if count > 0 => {
            let variance = data.iter().map(|value| {
                let diff = data_mean - (*value as f32);

                diff * diff
            }).sum::<f32>() / count as f32;

            Some(variance.sqrt())
        },
        _ => None
    }
}

fn main() {
    let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];

    let data_mean = mean(&data);
    println!("Mean is {:?}", data_mean);

    let data_std_deviation = std_deviation(&data);
    println!("Standard deviation is {:?}", data_std_deviation);

    let zscore = match (data_mean, data_std_deviation) {
        (Some(mean), Some(std_deviation)) => {
            let diff = data[4] as f32 - mean;

            Some(diff / std_deviation)
        },
        _ => None
    };
    println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore);
}

Разное

Большие целые числа

num-badge cat-science-badge

Расчёт для целых чисел превышающих 128 бит возможен с помощью типа BigInt .

extern crate num;

use num::bigint::{BigInt, ToBigInt};

fn factorial(x: i32) -> BigInt {
    if let Some(mut factorial) = 1.to_bigint() {
        for i in 1..(x+1) {
            factorial = factorial * i;
        }
        factorial
    }
    else {
        panic!("Failed to calculate factorial!");
    }
}

fn main() {
    println!("{}! equals {}", 100, factorial(100));
}

Обработка текста

РецептКрейтыКатегории
Получение графем Unicodeunicode-segmentation-badgecat-encoding-badge
Проверка и извлечение логина из email-адресаregex-badge lazy_static-badgecat-text-processing-badge
Извлечения списка из текста уникальных #тэговregex-badge lazy_static-badgecat-text-processing-badge
Извлечение из текста номеров телефоновregex-badgecat-text-processing-badge
Фильтрация лог-файла с помощью нескольких регулярных выраженийregex-badgecat-text-processing-badge
Замена всех вхождений одного текстового шаблона на другойregex-badge lazy_static-badgecat-text-processing-badge
Реализация типажа FromStr для определённой пользователем structstd-badgecat-text-processing-badge

Регулярные выражения

Разбор строк

Веб-программирование

Обработка веб-страниц

РецептКрейтыКатегории
Извлечение всех ссылок из HTML-страницыreqwest-badge select-badgecat-net-badge
Проверка веб-страницы на наличие мёртвых ссылокreqwest-badge select-badge url-badgecat-net-badge
Извлечение всех уникальных ссылок из страницы в формате MediaWikireqwest-badge regex-badgecat-net-badge

Работа с унифицированными указателями ресурсов (URL)

РецептКрейтыКатегории
Парсинг URL из строки в значение типа Urlurl-badgecat-net-badge
Создание базового URL удалением сегментов путиurl-badgecat-net-badge
Создание новых URL из базового URLurl-badgecat-net-badge
Извлечение URL источника (тройки схема / хост / порт)url-badgecat-net-badge
Удаление параметров запроса из URLurl-badgecat-net-badge

Типы мультимедиа (MIME)

РецептКрейтыКатегории
Получение типа MIME из строкиmime-badgecat-encoding-badge
Получение типа MIME из имени файлаmime-badgecat-encoding-badge
Парсинг типа MIME из HTTP ответаmime-badge reqwest-badgecat-net-badge cat-encoding-badge

Клиенты

Извлечение ссылок

Извлечение всех ссылок из веб-страницы HTML

reqwest-badge select-badge cat-net-badge

Используется reqwest::get для выполнения HTTP GET-запроса и затем используется метод Document::from_read для разбора ответа как HTML документа. find с условием, что 'Name должен быть "a"' извлекает все ссылки. Вызов filter_map на результате Selection извлекает все URL адреса из ссылок с атрибутом "href", атрибуты можно получить с помощью метода attr (атрибут).

#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate select;

use select::document::Document;
use select::predicate::Name;

error_chain! {
   foreign_links {
       ReqError(reqwest::Error);
       IoError(std::io::Error);
   }
}

fn main() -> Result<()> {
    let res = reqwest::get("https://www.rust-lang.org/en-US/")?;

    Document::from_read(res)?
        .find(Name("a"))
        .filter_map(|n| n.attr("href"))
        .for_each(|x| println!("{}", x));

    Ok(())
}

Проверка веб-страницы на наличие неработающих ссылок

reqwest-badge select-badge url-badge cat-net-badge

В примере вызывается функция get_base_url чтобы получить базовый URL адрес. Если в документе найден нужный тэг, можно затем получить атрибут href через attr из базового тэга. Position::BeforePath для изначального URL адреса работает как адрес по умолчанию.

Затем производится итерация по ссылкам в документе и разбор с помощью url::ParseOptions и Url::parse). Далее выполняются запросы по полученным ссылкам с помощью функций из крейта reqwest и у соответствующих ответов проверяются его StatusCode.

#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate select;
extern crate url;

use std::collections::HashSet;

use url::{Url, Position};
use reqwest::StatusCode;
use select::document::Document;
use select::predicate::Name;

error_chain! {
  foreign_links {
      ReqError(reqwest::Error);
      IoError(std::io::Error);
      UrlParseError(url::ParseError);
  }
}

fn get_base_url(url: &Url, doc: &Document) -> Result<Url> {
    let base_tag_href = doc.find(Name("base")).filter_map(|n| n.attr("href")).nth(0);

    let base_url = base_tag_href.map_or_else(
        || Url::parse(&url[..Position::BeforePath]),
        Url::parse,
    )?;

    Ok(base_url)
}

fn check_link(url: &Url) -> Result<bool> {
    let res = reqwest::get(url.as_ref())?;

    Ok(res.status() != StatusCode::NOT_FOUND)
}

fn main() -> Result<()> {
    let url = Url::parse("https://www.rust-lang.org/en-US/")?;

    let res = reqwest::get(url.as_ref())?;
    let document = Document::from_read(res)?;

    let base_url = get_base_url(&url, &document)?;

    let base_parser = Url::options().base_url(Some(&base_url));

    let links: HashSet<Url> = document
        .find(Name("a"))
        .filter_map(|n| n.attr("href"))
        .filter_map(|link| base_parser.parse(link).ok())
        .collect();

    links
        .iter()
        .filter(|link| check_link(link).ok() == Some(false))
        .for_each(|x| println!("{} is broken.", x));

    Ok(())
}

Извлечение всех уникальных ссылок из разметки MediaWiki

reqwest-badge regex-badge cat-net-badge

Данный пример вытягивает исходный код MediaWiki-страницы используя reqwest::get и затем ищет все внутренние и внешние ссылки с помощью регулярных выражений и метода Regex::captures_iter. Используется Cow, чтобы избежать излишних выделений объектов String на куче.

Синтаксис ссылок MediaWiki описан здесь.

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;
extern crate reqwest;
extern crate regex;

use std::io::Read;
use std::collections::HashSet;
use std::borrow::Cow;
use regex::Regex;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Reqwest(reqwest::Error);
        Regex(regex::Error);
    }
}

fn extract_links(content: &str) -> Result<HashSet<Cow<str>>> {
    lazy_static! {
        static ref WIKI_REGEX: Regex =
            Regex::new(r"(?x)
                \[\[(?P<internal>[^\[\]|]*)[^\[\]]*\]\]    # internal links
                |
                (url=|URL\||\[)(?P<external>http.*?)[ \|}] # external links
            ").unwrap();
    }

    let links: HashSet<_> = WIKI_REGEX
        .captures_iter(content)
        .map(|c| match (c.name("internal"), c.name("external")) {
            (Some(val), None) => Cow::from(val.as_str().to_lowercase()),
            (None, Some(val)) => Cow::from(val.as_str()),
            _ => unreachable!(),
        })
        .collect();

    Ok(links)
}

fn main() -> Result<()> {
    let mut content = String::new();
    reqwest::get(
        "https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw",
    )?
        .read_to_string(&mut content)?;

    println!("{:#?}", extract_links(&content)?);

    Ok(())
}

Унифицированное Местонахождение Ресурса (Uniform Resource Location, URL)

Разбор строки в экземпляр типа Url

url-badge cat-net-badge

Метод parse из крейта url делает синтаксический разбор и валидацию некоторой &str в структуру Url. Поскольку входная строка может быть некорректной, то метод возвращает Result<Url, ParseError>.

Как только строка, представляющая собой URL адрес оказывается разобрана в структуру типа Url, на этом экземпляре можно вызвать любой нужный метод.

extern crate url;

use url::{Url, ParseError};

fn main() -> Result<(), ParseError> {
    let s = "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open";

    let parsed = Url::parse(s)?;
    println!("The path part of the URL is: {}", parsed.path());

    Ok(())
}

Получение базового URL адреса удалением сегментов пути

url-badge cat-net-badge

Базовый URL адрес включает в себя протокол и домен. Базовые URL адреса не имеют каталогов, файлов или параметров адресной строки. Каждый из этих элементов вырезается из переданного URL адреса. PathSegmentsMut::clear удаляет путь и Url::set_query удаляет строку параметров.

#[macro_use]
extern crate error_chain;
extern crate url;

use url::Url;

error_chain! {
    foreign_links {
        UrlParse(url::ParseError);
    }
    errors {
        CannotBeABase
    }
}

fn main() -> Result<()> {
    let full = "https://github.com/rust-lang/cargo?asdf";

    let url = Url::parse(full)?;
    let base = base_url(url)?;

    assert_eq!(base.as_str(), "https://github.com/");
    println!("The base of the URL is: {}", base);

    Ok(())
}

fn base_url(mut url: Url) -> Result<Url> {
    match url.path_segments_mut() {
        Ok(mut path) => {
            path.clear();
        }
        Err(_) => {
            return Err(Error::from_kind(ErrorKind::CannotBeABase));
        }
    }

    url.set_query(None);

    Ok(url)
}

Создание новых URL адресов из базового URL адреса

url-badge cat-net-badge

Метод join создаёт новый URL адрес из базового и относительного пути.

extern crate url;

use url::{Url, ParseError};

fn main() -> Result<(), ParseError> {
    let path = "/rust-lang/cargo";

    let gh = build_github_url(path)?;

    assert_eq!(gh.as_str(), "https://github.com/rust-lang/cargo");
    println!("The joined URL is: {}", gh);

    Ok(())
}

fn build_github_url(path: &str) -> Result<Url, ParseError> {
    const GITHUB: &'static str = "https://github.com";

    let base = Url::parse(GITHUB).expect("hardcoded URL is known to be valid");
    let joined = base.join(path)?;

    Ok(joined)
}

Извлечение начала URL адреса (схема / хост / порт)

url-badge cat-net-badge

Тип Url выставляет разные методы для извлечения различной информации из URL адреса.

extern crate url;

use url::{Url, Host, ParseError};

fn main() -> Result<(), ParseError> {
    let s = "ftp://rust-lang.org/examples";

    let url = Url::parse(s)?;

    assert_eq!(url.scheme(), "ftp");
    assert_eq!(url.host(), Some(Host::Domain("rust-lang.org")));
    assert_eq!(url.port_or_known_default(), Some(21));
    println!("The origin is as expected!");

    Ok(())
}

origin выдаёт тот же самый результат.

#[macro_use]
extern crate error_chain;
extern crate url;

use url::{Url, Origin, Host};

error_chain! {
    foreign_links {
        UrlParse(url::ParseError);
    }
}

fn main() -> Result<()> {
    let s = "ftp://rust-lang.org/examples";

    let url = Url::parse(s)?;

    let expected_scheme = "ftp".to_owned();
    let expected_host = Host::Domain("rust-lang.org".to_owned());
    let expected_port = 21;
    let expected = Origin::Tuple(expected_scheme, expected_host, expected_port);

    let origin = url.origin();
    assert_eq!(origin, expected);
    println!("The origin is as expected!");

    Ok(())
}

Удаление идентификаторов фрагментов и пар "параметр-значение" из URL адреса

url-badge cat-net-badge

Пример делает разбор Url и разбивает их с помощью url::Position чтобы отрезать ненужные части URL адреса.


extern crate url;

use url::{Url, Position, ParseError};

fn main() -> Result<(), ParseError> {
    let parsed = Url::parse("https://github.com/rust-lang/rust/issues?labels=E-easy&state=open")?;
    let cleaned: &str = &parsed[..Position::AfterPath];
    println!("cleaned: {}", cleaned);
    Ok(())
}

Типы Медиа

Получение MIME типа из строки

mime-badge cat-encoding-badge

Этот пример показывает, как осуществить разбор в тип MIME из некоторой строки при помощи функций из крейта mime. FromStrError выводит MIME тип по умолчанию для параметра, переданного в unwrap_or.

extern crate mime;
use mime::{Mime, APPLICATION_OCTET_STREAM};

fn main() {
    let invalid_mime_type = "i n v a l i d";
    let default_mime = invalid_mime_type
        .parse::<Mime>()
        .unwrap_or(APPLICATION_OCTET_STREAM);

    println!(
        "MIME for {:?} used default value {:?}",
        invalid_mime_type, default_mime
    );

    let valid_mime_type = "TEXT/PLAIN";
    let parsed_mime = valid_mime_type
        .parse::<Mime>()
        .unwrap_or(APPLICATION_OCTET_STREAM);

    println!(
        "MIME for {:?} was parsed as {:?}",
        valid_mime_type, parsed_mime
    );
}

Получение MIME типа из строки

mime-badge cat-encoding-badge

Этот пример показывает, как осуществить разбор в тип MIME из некоторой строки при помощи функций из крейта mime. FromStrError выводит MIME тип по умолчанию для параметра, переданного в unwrap_or.

extern crate mime;
use mime::{Mime, APPLICATION_OCTET_STREAM};

fn main() {
    let invalid_mime_type = "i n v a l i d";
    let default_mime = invalid_mime_type
        .parse::<Mime>()
        .unwrap_or(APPLICATION_OCTET_STREAM);

    println!(
        "MIME for {:?} used default value {:?}",
        invalid_mime_type, default_mime
    );

    let valid_mime_type = "TEXT/PLAIN";
    let parsed_mime = valid_mime_type
        .parse::<Mime>()
        .unwrap_or(APPLICATION_OCTET_STREAM);

    println!(
        "MIME for {:?} was parsed as {:?}",
        valid_mime_type, parsed_mime
    );
}

Разбор MIME типа из HTTP ответа

reqwest-badge mime-badge cat-net-badge cat-encoding-badge

В ответе запроса по HTTP через reqwest найти MIME type-тип или тип мультимедиа может быть возможно через заголовок Content-Type. reqwest::header::HeaderMap::get разбирает содержимое заголовка и делает любое значение доступным как reqwest::header::HeaderValue, которое может быть преобразовано в строку. Крейт mime может затем произвести разбор этой строки и выдать значение типа mime::Mime.

Крейт mime также определяет широко используемые типы MIME.

Заметим, что модуль reqwest::header экспортируется из крейта http.

#[macro_use]
extern crate error_chain;
extern crate mime;
extern crate reqwest;

use mime::Mime;
use std::str::FromStr;
use reqwest::header::CONTENT_TYPE;


error_chain! {
   foreign_links {
       Reqwest(reqwest::Error);
       Header(reqwest::header::ToStrError);
       Mime(mime::FromStrError);
   }
}

fn main() -> Result<()> {
    let response = reqwest::get("https://www.rust-lang.org/logos/rust-logo-32x32.png")?;
    let headers = response.headers();

    match headers.get(CONTENT_TYPE) {
        None => {
            println!("The response does not contain a Content-Type header.");
        }
        Some(content_type) => {
            let content_type = Mime::from_str(content_type.to_str()?)?;
            let media_type = match (content_type.type_(), content_type.subtype()) {
                (mime::TEXT, mime::HTML) => "a HTML document",
                (mime::TEXT, _) => "a text document",
                (mime::IMAGE, mime::PNG) => "a PNG image",
                (mime::IMAGE, _) => "an image",
                _ => "neither text nor image",
            };

            println!("The reponse contains {}.", media_type);
        }
    };

    Ok(())
}

Клиенты

Выполнение запросов

Выполнение HTTP GET-запроса

reqwest-badge cat-net-badge

В примере разбирается переданный адрес URL и выполняется синхронный HTTP GET запрос с помощью reqwest::get. Затем печатается статус ответа reqwest::Response и его заголовки. Также читается тело HTTP ответа в предварительно выделенную строку String используя read_to_string.

#[macro_use]
extern crate error_chain;
extern crate reqwest;

use std::io::Read;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        HttpRequest(reqwest::Error);
    }
}

fn main() -> Result<()> {
    let mut res = reqwest::get("http://httpbin.org/get")?;
    let mut body = String::new();
    res.read_to_string(&mut body)?;

    println!("Status: {}", res.status());
    println!("Headers:\n{:#?}", res.headers());
    println!("Body:\n{}", body);

    Ok(())
}

Вызов Веб API

Запрос к API GitHub

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

Пример посылает запрос к GitHub stargazers API v3 используя метод reqwest::get, чтобы получить список всех пользователей, кто отметил какой-то проект на GitHub звёздочкой. reqwest::Response десериализуется с помощью Response::json в объекты типа User, которые реализуют типаж serde::Deserialize.

[tokio::main] используется, чтобы установить асинхронный движок и обрабатывать ожидания выполнения для [reqwet::get] до обработки запросов и преобразования в значения типа User.

use serde::Deserialize;
use reqwest::Error;

#[derive(Deserialize, Debug)]
struct User {
    login: String,
    id: u32,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers",
                              owner = "rust-lang-nursery",
                              repo = "rust-cookbook");
    println!("{}", request_url);
    let response = reqwest::get(&request_url).await?;

    let users: Vec<User> = response.json().await?;
    println!("{:?}", users);
    Ok(())
}

Проверка существования ресурса в API

reqwest-badge cat-net-badge

Данный рецепт запрашивает точку Users широко известного сервиса GitHub с использованием метода HEAD (Client::head) и затем смотрит код ответа для определения того, был ли вызов успешен или нет. Такой способ подходит для того, чтобы быстро "ткнуть" в REST ресурс без необходимости загружать тело ответа. reqwest::Client сконфигурированный с ClientBuilder::timeout, который гарантирует то, что запрос не будет длиться дольше данного временного интервала.

Из-за возвращения методами ClientBuilder::build и [ReqwestBuilder::send] типов ошибки reqwest::Error, используется псевдоним reqwest::Result для возвращаемого значения функции main.

use reqwest::Result;
use std::time::Duration;
use reqwest::ClientBuilder;

#[tokio::main]
async fn main() -> Result<()> {
    let user = "ferris-the-crab";
    let request_url = format!("https://api.github.com/users/{}", user);
    println!("{}", request_url);

    let timeout = Duration::new(5, 0);
    let client = ClientBuilder::new().timeout(timeout).build()?;
    let response = client.head(&request_url).send().await?;

    if response.status().is_success() {
        println!("{} is a user!", user);
    } else {
        println!("{} is not a user!", user);
    }

    Ok(())
}

Создать и удалить Gist через GitHub API

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

Пример создаёт фрагмент кода (в терминах GitHub называется gist) с помощью POST-запроса gists API v3 используя Client::post и удаляет только что созданный фрагмент с помощью DELETE-запроса используя Client::delete.

Объект reqwest::Client отвечает за детали для обоих типов запросов выше, детали включают URL адрес, тело запроса и аутентификацию. Тело POST-запроса как JSON можно подготовить с помощью макроса serde_json::json!. Вызов RequestBuilder::json определяет тело запроса. RequestBuilder::basic_auth управляет аутентификацией. А вызов RequestBuilder::send синхронно выполняет созданные запросы.

use error_chain::error_chain;
use serde::Deserialize;
use serde_json::json;
use std::env;
use reqwest::Client;

error_chain! {
    foreign_links {
        EnvVar(env::VarError);
        HttpRequest(reqwest::Error);
    }
}

#[derive(Deserialize, Debug)]
struct Gist {
    id: String,
    html_url: String,
}

#[tokio::main]
async fn main() ->  Result<()> {
    let gh_user = env::var("GH_USER")?;
    let gh_pass = env::var("GH_PASS")?;

    let gist_body = json!({
        "description": "the description for this gist",
        "public": true,
        "files": {
             "main.rs": {
             "content": r#"fn main() { println!("hello world!");}"#
            }
        }});

    let request_url = "https://api.github.com/gists";
    let response = Client::new()
        .post(request_url)
        .basic_auth(gh_user.clone(), Some(gh_pass.clone()))
        .json(&gist_body)
        .send().await?;

    let gist: Gist = response.json().await?;
    println!("Created {:?}", gist);

    let request_url = format!("{}/{}",request_url, gist.id);
    let response = Client::new()
        .delete(&request_url)
        .basic_auth(gh_user, Some(gh_pass))
        .send().await?;

    println!("Gist {} deleted! Status code: {}",gist.id, response.status());
    Ok(())
}

Данный пример использует HTTP Basic Auth чтобы получить доступ к GitHub API. Но в промышленном коде типичный пример использования может задействовать гораздо более сложный OAuth процесс авторизации.

Обращение к RESTful API с постраничной обработкой

reqwest-badge serde-badge cat-net-badge cat-encoding-badge

Пример оборачивает метода веб интерфейса в удобный итератор на Rust. Этот итератор лениво запрашивает следующую страницу результатов от удалённого сервера по достижении конца каждой страницы.

use reqwest::Result;
use serde::Deserialize;

#[derive(Deserialize)]
struct ApiResponse {
    dependencies: Vec<Dependency>,
    meta: Meta,
}

#[derive(Deserialize)]
struct Dependency {
    crate_id: String,
}

#[derive(Deserialize)]
struct Meta {
    total: u32,
}

struct ReverseDependencies {
    crate_id: String,
    dependencies: <Vec<Dependency> as IntoIterator>::IntoIter,
    client: reqwest::blocking::Client,
    page: u32,
    per_page: u32,
    total: u32,
}

impl ReverseDependencies {
    fn of(crate_id: &str) -> Result<Self> {
        Ok(ReverseDependencies {
               crate_id: crate_id.to_owned(),
               dependencies: vec![].into_iter(),
               client: reqwest::blocking::Client::new(),
               page: 0,
               per_page: 100,
               total: 0,
           })
    }

    fn try_next(&mut self) -> Result<Option<Dependency>> {
        if let Some(dep) = self.dependencies.next() {
            return Ok(Some(dep));
        }

        if self.page > 0 && self.page * self.per_page >= self.total {
            return Ok(None);
        }

        self.page += 1;
        let url = format!("https://crates.io/api/v1/crates/{}/reverse_dependencies?page={}&per_page={}",
                          self.crate_id,
                          self.page,
                          self.per_page);

        let response = self.client.get(&url).send()?.json::<ApiResponse>()?;
        self.dependencies = response.dependencies.into_iter();
        self.total = response.meta.total;
        Ok(self.dependencies.next())
    }
}

impl Iterator for ReverseDependencies {
    type Item = Result<Dependency>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.try_next() {
            Ok(Some(dep)) => Some(Ok(dep)),
            Ok(None) => None,
            Err(err) => Some(Err(err)),
        }
    }
}

fn main() -> Result<()> {
    for dep in ReverseDependencies::of("serde")? {
        println!("reverse dependency: {}", dep?.crate_id);
    }
    Ok(())
}

Скачивание файлов

Скачивание файла во временный каталог

reqwest-badge tempdir-badge cat-net-badge cat-filesystem-badge

Пример создаёт временный каталог с помощью tempfile::Builder и синхронно скачивает файл через HTTP используя reqwest::get.

Далее создаёт целевой файл File с именем, полученным от Response::url в каталоге tempdir() и копирует скачанные данные в этот файл посредством io::copy. Временный каталог автоматически удаляется после возврата из функции run.

#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate tempfile;

use std::io::copy;
use std::fs::File;
use tempfile::Builder;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        HttpRequest(reqwest::Error);
    }
}

fn main() -> Result<()> {
    let tmp_dir = Builder::new().prefix("example").tempdir()?;
    let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png";
    let mut response = reqwest::get(target)?;

    let mut dest = {
        let fname = response
            .url()
            .path_segments()
            .and_then(|segments| segments.last())
            .and_then(|name| if name.is_empty() { None } else { Some(name) })
            .unwrap_or("tmp.bin");

        println!("file to download: '{}'", fname);
        let fname = tmp_dir.path().join(fname);
        println!("will be located under: '{:?}'", fname);
        File::create(fname)?
    };
    copy(&mut response, &mut dest)?;
    Ok(())
}

Отправить POST-запросом файл в paste-rs

reqwest-badge cat-net-badge

reqwest::Client устанавливает соединение с https://paste.rs в соответствии с шаблоном reqwest::RequestBuilder. Вызов Client::post с передачей туда URL-адреса устанавливает связь с пунктом назначения, а RequestBuilder::body устанавливает тело запроса для отсылки через чтение из файла, и, наконец, RequestBuilder::send блокирует выполнение до тех пор, пока файл не отправится и не вернётся ответ. read_to_string возвращает ответ и выводит его на экран в консоль.

extern crate reqwest;

#[macro_use]
extern crate error_chain;

use std::fs::File;
use std::io::Read;
use reqwest::Client;

error_chain! {
    foreign_links {
        HttpRequest(reqwest::Error);
        IoError(::std::io::Error);
    }
}

fn main() -> Result<()> {
    let paste_api = "https://paste.rs";
    let file = File::open("message")?;

    let mut response = Client::new().post(paste_api).body(file).send()?;
    let mut response_body = String::new();
    response.read_to_string(&mut response_body)?;
    println!("Your paste is located at: {}", response_body);
    Ok(())
}

Частичная загрузка по диапазону в заголовке HTTP

reqwest-badge cat-net-badge

Используется reqwest::Client::head для получения Content-Length длины ответа.

Затем код использует reqwest::Client::get чтобы скачать содержимое в частей по 10240 байта, и в течение этого процесса печатая сообщения консоль. Заголовок Range определяет размер порции и текущую позицию в данной последовательности частей.

Диапазон в заголовке определяется в RFC7233.

use error_chain::error_chain;
use reqwest::header::{HeaderValue, CONTENT_LENGTH, RANGE};
use reqwest::StatusCode;
use std::fs::File;
use std::str::FromStr;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        Reqwest(reqwest::Error);
        Header(reqwest::header::ToStrError);
    }
}

struct PartialRangeIter {
  start: u64,
  end: u64,
  buffer_size: u32,
}

impl PartialRangeIter {
  pub fn new(start: u64, end: u64, buffer_size: u32) -> Result<Self> {
    if buffer_size == 0 {
      Err("invalid buffer_size, give a value greater than zero.")?;
    }
    Ok(PartialRangeIter {
      start,
      end,
      buffer_size,
    })
  }
}

impl Iterator for PartialRangeIter {
  type Item = HeaderValue;
  fn next(&mut self) -> Option<Self::Item> {
    if self.start > self.end {
      None
    } else {
      let prev_start = self.start;
      self.start += std::cmp::min(self.buffer_size as u64, self.end - self.start + 1);
      Some(HeaderValue::from_str(&format!("bytes={}-{}", prev_start, self.start - 1)).expect("string provided by format!"))
    }
  }
}

fn main() -> Result<()> {
  let url = "https://httpbin.org/range/102400?duration=2";
  const CHUNK_SIZE: u32 = 10240;
    
  let client = reqwest::blocking::Client::new();
  let response = client.head(url).send()?;
  let length = response
    .headers()
    .get(CONTENT_LENGTH)
    .ok_or("response doesn't include the content length")?;
  let length = u64::from_str(length.to_str()?).map_err(|_| "invalid Content-Length header")?;
    
  let mut output_file = File::create("download.bin")?;
    
  println!("starting download...");
  for range in PartialRangeIter::new(0, length - 1, CHUNK_SIZE)? {
    println!("range {:?}", range);
    let mut response = client.get(url).header(RANGE, range).send()?;
    
    let status = response.status();
    if !(status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT) {
      error_chain::bail!("Unexpected server response: {}", status)
    }
    std::io::copy(&mut response, &mut output_file)?;
  }
    
  let content = response.text()?;
  std::io::copy(&mut content.as_bytes(), &mut output_file)?;

  println!("Finished with success!");
  Ok(())
}

Аутентификация