Как "готовить" Rust
Эта книга Rust. Сборник рецептов является коллекцией простых примеров, которые демонстрируют хорошие практики, чтобы выполнять стандартные программистские задачи, используя библиотеки из экосистемы Rust.
Вы можете подробнее прочитать о Rust. Сборник рецептов, включая советы о том, как читать книгу, как использовать примеры и заметки о принятых соглашениях.
Соавторам
Этот проект подразумевает лёгкость вхождения для новичков в программировании на Rust, а также является одним из самых лёгких путей, чтобы быть вовлечённым в Rust-сообщество. Помощь всегда приветствуется. Подробнее смотрите CONTRIBUTING.md
Алгоритмы
Командная строка
Рецепт | Крейты | Категории |
---|---|---|
Разбор аргументов командной строки | ||
ANSI терминал |
Сжатие данных
Рецепт | Крейты | Категории |
---|---|---|
Распаковка tar-архива | ||
Запаковка каталога в tar-архив | ||
Распаковка tar-архива с удалением префиксов путей |
Конкурентность
Криптография
Рецепт | Крейты | Категории |
---|---|---|
Вычисление SHA-256 хеша для файла | ||
Подписание и проверка сообщения с помощью HMAC хеша | ||
Соление и хеширование пароля с PBKDF2 |
Структуры данных
Рецепт | Крейты | Категории |
---|---|---|
Определение и работа с типами, представленными в виде битовых полей |
Базы данных
Рецепт | Крейты | Категории |
---|---|---|
Создание SQLite базы данных | ||
Выборка и вставка данных | ||
Создание таблиц в Postgres | ||
Выборка и вставка данных | ||
Агрегирование данных |
Дата и время
Средства разработки
Отладка
Версионирование
Время сборки
Кодирование
Файловая система
Аппаратный доступ
Рецепт | Крейты | Категории |
---|---|---|
Проверка количества ядер процессора | Проверка количества ядер процессора |
Управление памятью
Рецепт | Крейты | Категории |
---|---|---|
Определение лениво вычисляемой константы |
Работа с сетью
Рецепт | Крейты | Категории |
---|---|---|
Прослушивание неиспользуемого TCP/IP порта |
Операционная система
Наука
Математика
Обработка текста
Веб-программирование
Обработка веб-страниц
Рецепт | Крейты | Категории |
---|---|---|
Извлечение всех ссылок из HTML-страницы | ||
Проверка веб-страницы на наличие мёртвых ссылок | ||
Извлечение всех уникальных ссылок из страницы в формате MediaWiki |
Работа с унифицированными указателями ресурсов (URL)
Типы мультимедиа (MIME)
Рецепт | Крейты | Категории |
---|---|---|
Получение типа MIME из строки | ||
Получение типа MIME из имени файла | ||
Парсинг типа MIME из HTTP ответа |
Клиенты
Немного о том, как "готовить" Rust
Содержание
- Для кого эта книга
- Как читать эту книгу
- Как использовать рецепты
- Несколько слов об обработке ошибок
- Несколько слов о выборе крейтов
Для кого эта книга
Эта книга предназначена для новичков в Rust, таким образом они могут быстро получить обзор возможностей экосистемы выросшей вокруг Rust. Книга также предназначена и для опытных разработчиков на Rust, которым иногда требуется взглянуть в рецепт, чтобы быстро вспомнить, как сделать какую-то обычную задачу, возникшую на практике.
Как читать эту книгу
Книга содержит указатель всех рецептов, организованных в разделы: "основы", "кодирование данных", "конкурентность" и так далее. Сами по себе разделы более или менее упорядочены по сложности, более поздние разделы обычно требуют подготовки, и иногда требуют понимания материала, встречающегося в предыдущих разделах.
В указателе каждый раздел содержит список рецептов. Рецепты представляют собой постановку задачи, например "сгенерировать случайные числа из диапазона"; и каждый рецепт помечен "шильдиками", показывающими какие крейты они используют (крейты (crates) - так называются библиотеки в экосистеме Rust), например , а также к каким категориям в crates.io эти крейты принадлежат, например .
Новичкам скорее всего будет комфортно читать последовательно с первого раздела до последнего и такое чтение даст хорошее представление об экосистеме. Нажимайте на заголовок раздела в указателе или на боковой панели, чтобы перейти прямо к нужному разделу в книге.
Если вы просто ищете решение для какой-то простой задачи, эта книга на данный момент более трудна для такого поиска. Самый простой путь чтобы найти нужный рецепт - это просмотреть указатель на предмет нужных крейтов и категорий, которые вас интересуют. И там уже нажимайте на название рецепта, чтобы посмотреть его полностью. Эту неэффективность мы исправим в будущем.
Как использовать рецепты
Рецепты построены так, чтобы дать вам мгновенный доступ к работающему коду наряду с полным объяснением, как он работает и дать вам пути, для получения дальнейшей информации по этой теме.
Все рецепты в этом сборнике являются полными, самодостаточными программами и могут быть скопированы прямо в ваш проект для дальнейших экспериментов с ними. Чтобы сделать это следуйте инструкциям ниже.
Рассмотрим пример "сгенерировать случайные числа из диапазона":
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::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>()); }
Генерация случайных чисел из диапазона
Пример генерирует случайные числа из полуоткрытого диапазона [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]
По умолчанию, случайные числа из крейта 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(())
}
Генерация случайных чисел пользовательского типа
Пример случайно генерирует пару (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);
}
Создание случайных паролей из букв и цифр
Пример случайно генерирует строку с заданной длиной из 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);
}
Создание случайных паролей из заданного множества символов
Пример случайно генерирует строку заданной длины из 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); }
Сортировка вектора
Сортировка вектора целых чисел
Этот пример сортирует вектор целых чисел с помощью метода 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]); }
Сортировка вектора вещественных чисел
Вектор из 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]); }
Сортировка вектора структур
Пример сортирует вектор персон (то есть структур типа 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), ]); }
Командная строка
Рецепт | Крейты | Категории |
---|---|---|
Разбор аргументов командной строки | ||
ANSI терминал |
Основы Clap
Разбор аргументов командной строки
Это приложение описывает структуру своего интерфейса командной строки используя стиль цепочек вызовов из 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
крейта и как он используется для отображения текста с различным цветом, форматированием, к примеру, жирный синий или жёлтый подчёркнутый текст, на 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-архива | ||
Запаковка каталога в tar-архив | ||
Распаковка tar-архива с удалением префиксов путей |
Работа с tar-архивами
Распаковка tar-архива
Пример разжимает с помощью (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-архив
Сжатие каталога /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-архива с удалением префиксов путей
Код в примере итерируется с помощью 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, который содержит структуры данных и функции для параллельного и многопоточного программирования. 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-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. 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 файлов
Этот пример рассчитывает 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
, библиотеку с примитивами для параллелизма данных в 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::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::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)); }
Параллельная сортировка вектора
Этот пример параллельно сортирует вектор строк.
Более детально, пример создаёт вектор пустых строк, а затем, используя 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::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 хеша для файла | ||
Подписание и проверка сообщения с помощью HMAC хеша | ||
Соление и хеширование пароля с PBKDF2 |
Хэширование
Вычисление SHA-256 хеша для файла
Код в примере пишет некоторые данные в файл на диске, затем вычисляет 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::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::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(())
}
Структуры данных
Рецепт | Крейты | Категории |
---|---|---|
Определение и работа с типами, представленными в виде битовых полей |
Пользовательские структуры данных
Определение и работа с типами, представленными в виде битовых полей
Пример создаёт тип 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 базы данных | ||
Выборка и вставка данных | ||
Создание таблиц в Postgres | ||
Выборка и вставка данных | ||
Агрегирование данных |
SQLite
Создание базы данных SQLite
Пакет 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(()) }
Выборка и вставка данных
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(()) }
Использование транзакций
[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
для создания таблиц в 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(()) }
Вставка и чтение данных
Этот рецепт вставляет данные в таблицу 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(()) }
Агрегирование данных
Этот рецепт выводит в порядке убывания национальности первых 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(()) }
Дата и время
Длительность и вычисление интервалов
Измерение прошедшего времени между двумя моментами выполнения кода
Пример измеряет количество времени 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); }
Проверяемое вычисление даты и времени
Код в примере вычисляет и отображает дату и время через 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."), } }
Преобразование локального времени во время в другом часовом поясе
Этот пример получает локальное время и отображает его с помощью 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)); }
Разбор и отображение
Просмотр даты и времени
Код получает текущее 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 и обратно
Пример преобразует дату, переданную через 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); }
Форматированный вывод даты и времени
Пример получает и отображает текущее время в 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
Код совершает разбор в структуру 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(()) }
Средства разработки
Отладка
Версионирование
Время сборки
Отладка
Диагностические сообщения
Вывод отладочного сообщения в консоль
Крейт 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
Вывод сообщения об ошибке в консоль
Правильная обработка ошибок рассматривает исключительные ситуации как, ну... исключительные.
Здесь ошибка выводится в 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
Пример создаёт особую конфигурацию логгера используя 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"); }
Логирование сообщений с помощью пользовательского логгера
Реализует особый логгер в консоль 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
Код в примере выводит сообщения в 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."); }
Конфигурация логирования
Включение уровней логирования для каждого модуля
Крейты с двумя модулями 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
Использование переменной среды для настройки логирования
Структура 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"); }
Включить метку времени в сообщения лога
Создаёт пользовательскую конфигурацию логгера с помощью 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
Логирование сообщений в пользовательское место
Крейт 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::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::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(()) }
Проверка того, является ли версия предварительным релизом
Даны две версии, 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(()) }
Нахождение самой последней версии из заданного диапазона
Дан список версий в виде &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(()) }
Проверка версии внешней команды на совместимость
Код в рецепте выполняет 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
Чтобы покрыть сценарии, где требуется использовать дополнительный код на 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++
Компоновка с библиотекой на 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 с использованием особых директив
На самом деле довольно просто собрать код на 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();
}
}
Кодирование
Наборы символов
Процент (или URL) кодирование строки
Кодирование входной строки с помощью 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
Кодирует строку в синтаксис 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
предоставляет метод 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
с использованием функции 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 записи в 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 с 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 записей, соответствующих предикату
Возвращает только строки из 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 часто содержат неверные данные. Для этих случаев крейт 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 формат
В этом примере показано, как сериализовать 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, используя крейт 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 файла, содержащего имя цвета и шестнадцатеричный цвет в файл с именем цвета и 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
предоставляет функцию 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 в универсальное 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
поможет развернуть значимые байты структурированных данных. Это может быть необходимо при получении информации по сети, в случае если полученные байты поступают из другой системы.
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) }
Обработка ошибок
Рецепт | Крейты | Категории |
---|---|---|
Корректная обработка ошибок в функции main | ||
Избегание отбрасывания ошибок в преобразованиях между ними | ||
Получение трассировки в сложных сценариях обработки ошибок |
Обработка ошибок
Правильная обработка ошибки в main функции
Обрабатывает ошибку, возникающую при попытке открыть файл, который не существует. Это достигается с помощью библиотеки 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 делает сопоставление с различными типами ошибок, возвращаемых функцией, возможным и относительно компактным. 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) сложных сценариев ошибок
Этот рецепт показывает, как обрабатывать сложный сценарий ошибки, а затем напечатать обратную трассировку. Он полагается на 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
связанную с этой ошибкой.
Файловая система
Чтение и запись
Чтение строк из файла
Записывает в файл сообщение из трёх строк, а затем читает его обратно по строчке за раз с помощью итератора 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::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 и моделирует некоторые непоследовательные чтения из файла. Использование карты памяти означает, что вы просто индексируете срезы, а не делаете с помощью 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 часа
Получение текущего рабочего каталога, вызывая 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::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") )) ); }
Рекурсивно найти повторяющиеся имена файлов
Найти рекурсивно в текущем каталоге дубликаты имён файлов, печатая их только один раз.
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); } } }
Рекурсивно найти все файлы с заданным предикатом
Найти 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(()) }
Обход каталогов с пропуском файлов с точкой в начале имени
Используется 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::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 файлы
В этом примере выполняется задача рекурсивно найти все файлы 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(()) }
Найти все файлы с заданным шаблоном, игнорируя регистр в имени файла
Найдите все файлы изображений в каталоге /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(()) }
Аппаратный доступ
Рецепт | Крейты | Категории |
---|---|---|
Проверка количества ядер процессора | Проверка количества ядер процессора |
Процессор
Проверка количество логических ядер процессора
Показывает количество логических ядер процессора на текущей машине, используя [ num_cpus::get
].
extern crate num_cpus; fn main() { println!("Number of logical cores is {}", num_cpus::get()); }
Управление памятью
Рецепт | Крейты | Категории |
---|---|---|
Определение лениво вычисляемой константы |
Константы
Объявление лениво вычисляемой константы
Объявляет лениво вычисляемую константу типа 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 порта |
Сервер
Прослушивание неиспользуемого порта TCP/IP
В этом примере порт отображается в консоли и программа будет прослушивать, пока на него не будет сделан запрос. 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(()) }
Операционная система
Внешняя команда
Запуск внешне команды и обработка стандартного вывода
Запускает 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, и проверка кода ошибки
Открывает интерпретатор 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) } }
Запуск внешние команды цепочками
Показывает до десяти самых больших файлов и подкаталогов в текущем рабочем каталоге. Это эквивалентно запуску: 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 дочернего процесса в один файл
Создаёт дочерний процесс и перенаправляет 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(()) }
Постоянная обработка вывода дочернего процесса
В разделе Выполнить внешнюю команду и обработать её стандартный вывод обработка не начинается до тех пор, пока не завершится внешняя 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(()) }
Наука
Математика
Математика
Линейная алгебра
Сложение матриц
Создаёт две двумерные матрицы (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::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)); }
Умножение скаляра на вектор и на матрицу
Создаёт одномерный 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 поддерживает несколько способов создания массивов - этот рецепт создаёт 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.])); }
Норма вектора
Этот рецепт демонстрирует использование типа 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 ## Инвертирование матрицы
Создаёт матрицу 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!"); } } }
Тригонометрия
Расчёт длины стороны треугольника
Вычисляет длину гипотенузы прямоугольного треугольника с углом 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::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 ## Сложение комплексных чисел
Выполнение математических операций над комплексными числами является таким же как и для встроенных типов: рассматриваемые числа должны быть одного типа (то есть с плавающей точкой или целыми числами).
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 }
Статистика
Меры среднего значения
В этих примерах вычисляются показатели среднего значения для набора данных, содержащегося в 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); }
Разное
Большие целые числа
Расчёт для целых чисел превышающих 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)); }
Обработка текста
Регулярные выражения
Разбор строк
Веб-программирование
Обработка веб-страниц
Рецепт | Крейты | Категории |
---|---|---|
Извлечение всех ссылок из HTML-страницы | ||
Проверка веб-страницы на наличие мёртвых ссылок | ||
Извлечение всех уникальных ссылок из страницы в формате MediaWiki |
Работа с унифицированными указателями ресурсов (URL)
Типы мультимедиа (MIME)
Рецепт | Крейты | Категории |
---|---|---|
Получение типа MIME из строки | ||
Получение типа MIME из имени файла | ||
Парсинг типа MIME из HTTP ответа |
Клиенты
Извлечение ссылок
Извлечение всех ссылок из веб-страницы HTML
Используется 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(()) }
Проверка веб-страницы на наличие неработающих ссылок
В примере вызывается функция 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
Данный пример вытягивает исходный код 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
Метод 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 адрес включает в себя протокол и домен. Базовые 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 адреса
Метод 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
выставляет разные методы для извлечения различной информации из 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
и разбивает их с помощью 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
из некоторой строки при помощи функций из крейта 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
из некоторой строки при помощи функций из крейта 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 ответа
В ответе запроса по 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-запроса
В примере разбирается переданный адрес 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
Пример посылает запрос к 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
Данный рецепт запрашивает точку 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
Пример создаёт фрагмент кода (в терминах 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 с постраничной обработкой
Пример оборачивает метода веб интерфейса в удобный итератор на 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(()) }
Скачивание файлов
Скачивание файла во временный каталог
Пример создаёт временный каталог с помощью 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::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::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(()) }