1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Простые типы

Язык Rust имеет несколько типов, которые считаются «простыми» («примитивными»). Это означает, что они встроены в язык. Rust структурирован таким образом, что стандартная библиотека также предоставляет ряд полезных типов, построенных на базе этих простых типов, но это самые простые.

Логический тип

Rust имеет встроенный логический тип, называемый bool. Он может принимать два значения, true и false:

let x = true;

let y: bool = false;Run

Логические типы часто используются в конструкции if.

Вы можете найти больше информации о логических типах (bool) в документации к стандартной библиотеке (англ.).

Символы

Тип char представляет собой одиночное скалярное значение Unicode. Вы можете создать char с помощью одинарных кавычек: (')

let x = 'x';
let two_hearts = '💕';Run

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

Вы можете найти больше информации о символах (char) в документации к стандартной библиотеке (англ.).

Числовые типы

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

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

Если для числового литерала не указан тип, то он будет выведен по умолчанию:

let x = 42; // x имеет тип i32

let y = 1.0; // y имеет тип f64Run

Ниже представлен список различных числовых типов, со ссылками на их документацию в стандартной библиотеке:

Давайте пройдёмся по их категориям.

Знаковые и беззнаковые

Целые типы бывают двух видов: знаковые и беззнаковые. Чтобы понять разницу, давайте рассмотрим число с размером в четыре бита. Знаковые четырёхбитные числа, позволяют хранить значения от -8 до +7. Знаковые числа используют представление «дополнение до двух» (дополнительный код). Беззнаковые четырёхбитные числа, ввиду того что не нужно хранить отрицательные значения, позволяют хранить значения от 0 до +15.

Беззнаковые типы используют u для своей категории, а знаковые типы используют i. i означает «integer». Так, u8 представляет собой число без знака с размером восемь бит, а i8 представляет собой число со знаком с размером восемь бит.

Типы фиксированного размера

Типы с фиксированным размером соответственно имеют фиксированное количество бит в своём представлении. Допустимыми размерами являются 8, 16, 32, 64. Таким образом, u32 представляет собой целое число без знака с размером 32 бита, а i64 — целое число со знаком с размером 64 бита.

Типы переменного размера

Rust также предоставляет типы, размер которых зависит от размера указателя на целевой машине. Эти типы имеют «size» в названии в качестве признака размера, и могут быть знаковыми или беззнаковыми. Таким образом, существует два типа: isize и usize.

С плавающей точкой

В Rust также есть два типа с плавающей точкой: f32 и f64. Они соответствуют IEEE-754 числам с плавающей точкой одинарной и двойной точности соответственно.

Массивы

В Rust, как и во многих других языках программирования, есть типы-последовательности, для представления последовательностей неких вещей. Самый простой из них — это массив, то есть последовательность элементов одного и того же типа, имеющая фиксированный размер. Массивы неизменяемы по умолчанию.

let a = [1, 2, 3]; // a: [i32; 3]
let mut m = [1, 2, 3]; // m: [i32; 3]Run

Массивы имеют тип [T; N]. О значении T мы поговорим позже, когда будем рассматривать обобщённое программирование. N — это константа времени компиляции, представляющая собой длину массива.

Для инициализации всех элементов массива одним и тем же значением есть специальный синтаксис. В этом примере каждый элемент a будет инициализирован значением 0:

let a = [0; 20]; // a: [i32; 20]Run

Вы можете получить число элементов массива a с помощью метода a.len():

let a = [1, 2, 3];

println!("Число элементов в a: {}", a.len());Run

Вы можете получить определённый элемент массива с помощью индекса:

let names = ["Graydon", "Brian", "Niko"]; // names: [&str; 3]

println!("Второе имя: {}", names[1]);Run

Индексы нумеруются с нуля, как и в большинстве языков программирования, поэтому мы получаем первое имя с помощью names[0], а второе — с помощью names[1]. Пример выше печатает Второе имя: Brian. Если вы попытаетесь использовать индекс, который не входит в массив, вы получите ошибку: при доступе к массивам происходит проверка границ во время исполнения программы. Такая ошибочная попытка доступа — источник многих проблем в других языках системного программирования.

Вы можете найти больше информации о массивах (array) в документации к стандартной библиотеке (англ.).

Срезы

Срез — это ссылка на (или «проекция» в) другую структуру данных. Они полезны, когда нужно обеспечить безопасный, эффективный доступ к части массива без копирования. Например, возможно вам нужно сослаться на единственную строку файла, считанного в память. Из-за своей ссылочной природы, срезы создаются не напрямую, а из существующих связанных имён. У срезов есть длина, они могут быть изменяемы или неизменяемы.

Синтаксис срезов

Для создания срезов из различных сущностей можно использовать комбинации & и []. Символ & указывает на то, что срезы схожи со ссылками, а в квадратных скобках указывается диапазон, задающий длину среза:

let a = [0, 1, 2, 3, 4];
let complete = &a[..]; // Срез, содержащий все элементы массива `a`
let middle = &a[1..4]; // Срез `a`: только элементы 1, 2, и 3Run

Срезы имеют тип &[T]. О значении T мы поговорим позже, когда будем рассматривать обобщённое программирование.

Вы можете найти больше информации о срезах (slice) в документации к стандартной библиотеке (англ.).

str

Тип str в Rust является наиболее простым типом строк. Это безразмерный тип, поэтому сам по себе он не очень полезен, но он становится полезным при использовании ссылки, &str. Пока просто остановимся на этом.

Вы можете найти больше информации о строках (str) в документации к стандартной библиотеке (англ.).

Кортежи

Кортеж — это последовательность фиксированного размера. Вроде такой:

let x = (1, "привет");Run

Этот кортеж из двух элементов создан с помощью скобок и запятой между элементами. Вот тот же код, но с аннотациями типов:

let x: (i32, &str) = (1, "привет");Run

Как вы можете видеть, тип кортежа выглядит как сам кортеж, но места элементов занимают типы. Внимательные читатели также отметят, что кортежи гетерогенны: в этом кортеже одновременно хранятся значения типов i32 и &str. В языках системного программирования строки немного более сложны, чем в других языках. Пока вы можете читать &str как срез строки. Мы вскоре узнаем об этом больше.

Можно присваивать один кортеж другому, если они содержат значения одинаковых типов и имеют одинаковую арность. Арность кортежей одинакова, когда их длина совпадает.

let mut x = (1, 2); // x: (i32, i32)
let y = (2, 3); // y: (i32, i32)

x = y;Run

Стоит отметить и ещё один момент, касающийся длины кортежей: кортеж нулевой длины ((); пустой кортеж) часто называют «единичным значением». Соответственно, тип такого значения — «единичный тип».

Доступ к полям кортежа можно получить с помощью деконструирующего let. Вот пример:

let (x, y, z) = (1, 2, 3);

println!("x это {}", x);Run

Помните, мы говорили, что левая часть оператора let может больше, чем просто присваивать имена? Мы имели ввиду то, что приведено выше. Мы можем написать слева от let шаблон, и, если он совпадает со значением справа, произойдёт присваивание имён сразу нескольким значениям. В данном случае, let «деконструирует» или «разбивает» кортеж, и присваивает его части трём именам.

Это очень удобный шаблон программирования, и мы ещё не раз увидим его.

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

(0,); // одноэлементный кортеж
(0); // ноль в круглых скобкахRun

Индексация кортежей

Вы также можете получить доступ к полям кортежа с помощью индексации:

let tuple = (1, 2, 3);

let x = tuple.0;
let y = tuple.1;
let z = tuple.2;

println!("x is {}", x);Run

Как и в случае индексации массивов, индексы начинаются с нуля, но здесь, в отличие от массивов, используется ., а не [].

Вы можете найти больше информации о кортежах (tuple) в документации к стандартной библиотеке (англ.).

Функции

Функции тоже имеют тип! Это выглядит следующим образом:

fn foo(x: i32) -> i32 { x }

let x: fn(i32) -> i32 = foo;Run

В данном примере x — это «указатель на функцию», которая принимает в качестве аргумента i32 и возвращает i32.