[Перевод] Rust на примерах. Часть 1
Этот цикл статей является вольным переводом книги «Rust by Example», которую пишет Хорхе Апарисио на Github.На момент написания этого топика автор книги создал 49 глав, в первой части будет перевод первых пяти. Убедитесь, что Rust установлен и под рукой имеется документация.
Давайте начинать!
СодержаниеПривет, мир! Форматированный вывод Литералы и операторы Переменные Типы Примечание:
При написании программ использовался компилятор версии Nightly (0.10), не забудьте про это. Запустить код можно в Rust Playpen: http://play.rust-lang.org 1. Привет, мир! Это код традиционной программы «Hello World»: // Комментарии игнорируются компилятором
// Это основная функция fn main () { // Вывести текст в консоль println!(«Hello World!»); } println! это макрос (мы рассмотрим их позже), который печатает текст в консоль.Программа может быть сгенерирована с помощью компилятора Rust rustc:
$ rustc hello.rs rustc создаст бинарный файл «hello», который можно запустить: $ ./hello Hello World! 2. Форматированный вывод Макрос println! не только выводит в консоль, а также способен форматировать текст и сериализованные значения. Корректность проверяется во время компиляции. fn main () { // `print!`, как `println!`, но он не добавляет новую строку в конце print!(«January has »);
// `{}` это заполнители для аргументов, которые будут строками println!(»{} days», 31i); // `i` суффикс указывает компилятору, что этот литерал имеет тип: целое // число со знаком, смотрите следующую главу для более подробной информации
// Позиционные аргументы могут быть повторно использованы по шаблону println!(»{0}, this is {1}. {1}, this is {0}», «Alice», «Bob»);
// Аргументы можно называть println!(»{subject} {verb} {predicate}», predicate=«over the lazy dog», subject=«the quick brown fox», verb=«jumps»);
// Специальное форматирование может быть указано в заполнителе после `:`, `t` это бинарное представление println!(»{} of {: t} people know binary, the other half don’t», 1i, 2i);
// Ошибка! Не хватает аргумента для вывода println!(«My name is {0}, {1} {0}», «Bond»); // Исправьте ^ добавьте отсутствующий аргумент: «James» } Дополнительная информация о форматировании здесь: std:: fmt3. Литералы и операторы Целые числа 1, с плавающей точкой 1.2, символы 'a', строки «abc», логические true и блочные () типы могут быть выражены с помощью литералов.Также целые числа можно выразить через шестнадцатеричное, восьмеричное или двоичное обозначение, используя один из префиксов: 0x, 0o или 0b.
В числовые литералы можно вставлять подчёркивания для читабельности, например, 1_000 такой же, как и 1000, а 0.000_001 такой же, как и 0.000001.
Мы должны сказать компилятору, какой из литералов мы используем. Сейчас мы будем использовать суффикс u, указывающий, что литерал является целым числом без знака, суффикс i чтобы указать, что это знаковое целое число. Мы рассмотрим систему типов в 5 главе, а также подробную информацию о аннотировании литералов.
Доступные операторы и их приоритет похож на C-подобных языках.
fn main () { // Целочисленное сложение println!(»1 + 2 = {}», 1u + 2);
// Вычитание println!(»1 — 2 = {}», 1i — 2); // Попробуйте изменить `1i` на `1u` и понять, почему тип важен
// Булева логика println!(«true AND false is {}», true && false); println!(«true OR false is {}», true || false); println!(«NOT true is {}», ! true);
// Битовые операции println!(»0011 AND 0101 is {:04t}», 0b0011u & 0b0101); println!(»0011 OR 0101 is {:04t}», 0b0011u | 0b0101); println!(»0011 XOR 0101 is {:04t}», 0b0011u ^ 0b0101); println!(»1 << 5 is {}", 1u << 5); println!("0x80 >> 2 is 0x{: x}», 0×80u >> 2);
// Используйте подчеркивания, чтобы улучшить читаемость println!(«One million is written as {}», 1_000_000u); } 4. Переменные Значения (как и литералы) могут быть связаны с переменными, используя обозначение let. fn main () { let an_integer = 1u; let a_boolean = true; let unit = ();
// скопировать значение `an_integer` в `copied_integer` let copied_integer = an_integer;
println!(«An integer: {}», copied_integer); println!(«A boolean: {}», a_boolean); println!(«Meet the unit value: {}», unit);
// Компилятор предупреждает о неиспользуемых переменных; эти предупреждения можно // отключить используя подчёркивание перед именем переменной let _unused_variable = 3u; let noisy_unused_variable = 2u; // Исправьте ^ Добавьте подчёркивание } 4.1 Изменчивость По-умолчанию переменные нельзя изменять, но это можно исправить добавив модификатор mut. fn main () { let _immutable_variable = 1i; let mut mutable_variable = 1i;
println!(«Before mutation: {}», mutable_variable);
// Ок mutable_variable += 1;
println!(«After mutation: {}», mutable_variable);
// Ошибка! _immutable_variable += 1; } Компилятор будет выводить сообщения об ошибке изменчивости.4.2 Области и видимость Переменные имеют локальную область, и имеет видимость в блоке (блок представляет собой набор операторов, заключённых в фигурные скобки {}). Кроме того, допускается скрытие переменной. fn main () { // Эта переменная живет в области функции main let long_lived_variable = 1i;
// Это блок, он имеет меньший объем нежели основная функция { // Эта переменная существует только в этом блоке let short_lived_variable = 2i;
println!(«inner short: {}», short_lived_variable);
// Эта переменная не видна внешней функции let long_lived_variable = 5_f32;
println!(«inner long: {}», long_lived_variable); } // Конец блока
// Ошибка! `short_lived_variable` не существует в этой области println!(«outer short: {}», short_lived_variable); // Исправьте ^ Заккоментируйте строку
println!(«outer long: {}», long_lived_variable); } 4.3 «Первое объявление» Можно сперва объявлять переменные, а инициализировать их позже. Но эта форма редко используется, так как это может привести к использованию неинициализированных переменных. fn main () { // Объявляем переменную let a_variable;
{ let x = 2i;
// Инициализируем переменную a_variable = x * x; }
println!(«a variable: {}», a_variable);
let another_variable;
// Ошибка! Использование неинициализированной переменной println!(«another variable: {}», another_variable); // Исправьте ^ Заккоментируйте строку
another_variable = 1i;
println!(«another variable: {}», another_variable); } Компилятор запрещает использование неинициализированных переменных, так как это привело бы к непредсказуемым последствиям.5. Типы Rust обеспечивает безопасность с помощью статической проверки типов. Типы переменных могут быть аннотированы, когда объявляются. Тем не менее, в большинстве случаев, компилятор сможет определить тип переменной из контекста, это снижает время их аннотации. fn main () { // Аннотированный тип переменной let a_float: f64 = 1.0;
// Эта переменная типа `int` let mut an_integer = 5i;
// Ошибка! Тип переменной нельзя изменять an_integer = true; } Это краткое изложение примитивных типов в Rust: целые числа: i8, i16, i32, i64 и int (определяет компьютер) целые числа без знака: u8, u16, u32, u64 и uint (определяет компьютер) с плавающей точкой: f32, f64 char значения Unicode: 'a', 'α' и '∞' (4 байта каждый) bool true или false тип блока () 5.1 Приведение типов Rust не предоставляет неявного преобразования типов (coercion) между примитивными типами. Но, явное приведение типов (casting) может быть достигнуто с помощью ключевого слова as. fn main () { let decimal = 65.4321_f32;
// Ошибка! Нет неявного преобразования let integer: u8 = decimal; // Исправьте ^ Заккоментируйте строку
// Явное преобразование let integer = decimal as u8; let character = integer as char;
println!(«Casting: {} → {} → {}», decimal, integer, character); } 5.2 Литералы В числовых литералах тип может быть аннотирован, добавив тип в качестве суффикса, за исключением uint, использующей суффикс u и int, который использует суффикс i.Тип литералов без суффикса будет зависеть от того, как они используются. Если никаких ограничений не существует, то компилятор выдаст сообщение об ошибке.
fn main () { // Литералы с суффиксами, их вид известен при инициализации let x = 1u8; let y = 2u; let z = 3f32;
// Литералы без суффикса, их вид зависит от того, как они используются let i = 1; let f = 1.0;
// `size_of_val` возвращает размер переменной в байтах println!(«size of `x` in bytes: {}», std: mem: size_of_val (&x)); println!(«size of `y` in bytes: {}», std: mem: size_of_val (&y)); println!(«size of `z` in bytes: {}», std: mem: size_of_val (&z)); println!(«size of `i` in bytes: {}», std: mem: size_of_val (&i)); println!(«size of `f` in bytes: {}», std: mem: size_of_val (&f));
// Ограничения (слагаемые должны иметь тот же тип) для `i` и `f` let _constraint_i = x + i; let _constraint_f = z + f; // Заккоментируйте эти две строки } Есть некоторые понятия, используемые в предыдущем коде, которые не были объяснены раньше, вот краткое объяснение для нетерпеливых читателей: fun (&foo) используется, чтобы передать аргумент в функцию по ссылке, а не по значению fun (foo). std: mem: size_of_val является функцией, но вызывается с указанием полного пути. Код можно разделить на логические единицы, называемые модулями. Здесь функция size_of_val определена в модуле mem, а модуль mem определен в крэйте std. 5.3 Логический вывод Логический вывод типов довольно умён. Тип добавляемой переменной используется как определитель типа для второй переменной. Вот продвинутый пример: fn main () { // Использование локального вывода, компилятор знает, что `elem` имеет тип `u8` let elem = 5u8;
// Создадим пустой вектор (расширяемый массив) let mut vec = Vec: new (); // В этот момент компилятор не знает точный тип `vec`, он // просто знает, что это вектор `Vec<_>`
// Вставим `elem` в вектор
vec.push (elem);
// Ага! Теперь компилятор знает, что `vec` это вектор `u8` (`Vec
println!(»{}», vec); } Отсутствует необходимость в аннотации типа переменной, компилятор счастлив как и программист!5.4 Псевдонимы (алиасы) Оператор type может быть использован, чтобы задать новое имя существующему типу. Тип должен быть в стиле CamelCase, либо компилятор выдаст предупреждение. Исключением из этого правила являются примитивные типы: uint, f32 и другие. // `NanoSecond` это новое имя для `u64` type NanoSecond = u64; type Inch = u64;
// Используйте этот атрибут, чтобы не выводить предупреждение #[allow (non_camel_case_types)] type uint64_t = u64; // Попробуйте удалить атрибут
fn main () { // `NanoSecond` = `Inch` = `uint64_t` = `u64` let nanoseconds: NanoSecond = 5 as uint64_t; let inches: Inch = 2 as uint64_t;
// Обратите внимание, что псевдонимы новых типов не предоставляют
// дополнительную безопасность, из-за того, что они не нового типа
println!(»{} nanoseconds + {} inches = {} unit?»,
nanoseconds,
inches,
nanoseconds + inches);
}
Основное применение псевдонимов это снижение количества кода, например, тип IoResult