Rust на примерах. Часть 1

от автора

Этот цикл статей является вольным переводом книги «Rust by Example», которую пишет Хорхе Апарисио на Github.

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

Давайте начинать!

Содержание

  1. Привет, мир!
  2. Форматированный вывод
  3. Литералы и операторы
  4. Переменные
  5. Типы

Примечание:

  1. При написании программ использовался компилятор версии Nightly (0.10), не забудьте про это.
  2. Запустить код можно в 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​::fmt​

3. Литералы и операторы

Целые числа 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}", 0x80u >> 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<u8>`)     // Попробуйте заккоментировать строку `vec.push(elem)`      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<T> является псевдонимом типа Result<T, IoError>.

Заключение

Присоединяйтесь к google-группе: Rust по-русски для получения дополнительной информации по этому языку.

Все замечания, ошибки или неточности отправляйте мне в почту.

ссылка на оригинал статьи http://habrahabr.ru/post/232829/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *