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

в 22:36, , рубрики: Rust, параллельное программирование, примеры кода, Программирование

Этот цикл статей является вольным переводом книги «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 по-русски для получения дополнительной информации по этому языку.

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

Автор: seweb

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js