% Простые типы
Язык Rust имеет несколько типов, которые считаются «простыми» («примитивными»). Это означает, что они встроены в язык. Rust структурирован таким образом, что стандартная библиотека также предоставляет ряд полезных типов, построенных на базе этих простых типов, но это самые простые.
Rust имеет встроенный логический тип, называемый bool
. Он может принимать два
значения, true
и false
:
let x = true;
let y: bool = false;
Логические типы часто используются в конструкции if
.
Вы можете найти больше информации о логических типах (bool
) в документации к
стандартной библиотеке (англ.).
Тип char
представляет собой одиночное скалярное значение Unicode. Вы можете
создать char
с помощью одинарных кавычек: ('
)
let x = 'x';
let two_hearts = '💕';
Это означает, что в отличие от некоторых других языков, char
в Rust
представлен не одним байтом, а четырьмя.
Вы можете найти больше информации о символах (char
) в документации к
стандартной библиотеке (англ.).
Rust имеет целый ряд числовых типов, разделённых на несколько категорий: знаковые и беззнаковые, фиксированного и переменного размера, числа с плавающей точкой и целые числа.
Эти типы состоят из двух частей: категория и размер. Например, u16
представляет собой тип без знака с размером в шестнадцать бит. Чем большим
количеством бит представлен тип, тем большее число мы можем задать.
Если для числового литерала не указан тип, то он будет выведен по умолчанию:
let x = 42; // x имеет тип i32
let y = 1.0; // y имеет тип f64
Ниже представлен список различных числовых типов, со ссылками на их документацию в стандартной библиотеке:
Давайте пройдёмся по их категориям.
Целые типы бывают двух видов: знаковые и беззнаковые. Чтобы понять разницу,
давайте рассмотрим число с размером в четыре бита. Знаковые четырёхбитные числа,
позволяют хранить значения от -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]
Массивы имеют тип [T; N]
. О значении T
мы поговорим позже, когда будем
рассматривать обобщённое программирование. N
— это константа
времени компиляции, представляющая собой длину массива.
Для инициализации всех элементов массива одним и тем же значением есть
специальный синтаксис. В этом примере каждый элемент a
будет инициализирован
значением 0
:
let a = [0; 20]; // a: [i32; 20]
Вы можете получить число элементов массива a
с помощью метода a.len()
:
let a = [1, 2, 3];
println!("Число элементов в a: {}", a.len());
Вы можете получить определённый элемент массива с помощью индекса:
let names = ["Graydon", "Brian", "Niko"]; // names: [&str; 3]
println!("Второе имя: {}", names[1]);
Индексы нумеруются с нуля, как и в большинстве языков программирования, поэтому
мы получаем первое имя с помощью 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, и 3
Срезы имеют тип &[T]
. О значении T
мы поговорим позже, когда будем
рассматривать обобщённое программирование.
Вы можете найти больше информации о срезах (slice
) в документации к
стандартной библиотеке (англ.).
Тип str
в Rust является наиболее простым типом строк. Это
безразмерный тип, поэтому сам по себе он не очень полезен, но он
становится полезным при использовании ссылки, &str
. Пока просто
остановимся на этом.
Вы можете найти больше информации о строках (str
) в документации к
стандартной библиотеке (англ.).
Кортеж — это последовательность фиксированного размера. Вроде такой:
let x = (1, "привет");
Этот кортеж из двух элементов создан с помощью скобок и запятой между элементами. Вот тот же код, но с аннотациями типов:
let x: (i32, &str) = (1, "привет");
Как вы можете видеть, тип кортежа выглядит как сам кортеж, но места элементов
занимают типы. Внимательные читатели также отметят, что кортежи гетерогенны: в
этом кортеже одновременно хранятся значения типов i32
и &str
. В языках
системного программирования строки немного более сложны, чем в других языках.
Пока вы можете читать &str
как срез строки. Мы вскоре узнаем об этом больше.
Можно присваивать один кортеж другому, если они содержат значения одинаковых типов и имеют одинаковую арность. Арность кортежей одинакова, когда их длина совпадает.
let mut x = (1, 2); // x: (i32, i32)
let y = (2, 3); // y: (i32, i32)
x = y;
Стоит отметить и ещё один момент, касающийся длины кортежей: кортеж нулевой
длины (()
; пустой кортеж) часто называют «единичным значением».
Соответственно, тип такого значения — «единичный тип».
Доступ к полям кортежа можно получить с помощью деконструирующего let. Вот пример:
let (x, y, z) = (1, 2, 3);
println!("x это {}", x);
Помните, мы говорили, что левая часть оператора let
может больше, чем
просто присваивать имена? Мы имели ввиду то, что приведено выше. Мы можем
написать слева от let
шаблон, и, если он совпадает со значением справа,
произойдёт присваивание имён сразу нескольким значениям. В данном случае, let
«деконструирует» или «разбивает» кортеж, и присваивает его части трём именам.
Это очень удобный шаблон программирования, и мы ещё не раз увидим его.
Вы можете устранить неоднозначность трактовки для кортежа, состоящего из одного элемента, и значения в скобках с помощью запятой:
(0,); // одноэлементный кортеж
(0); // ноль в круглых скобках
Вы также можете получить доступ к полям кортежа с помощью индексации:
let tuple = (1, 2, 3);
let x = tuple.0;
let y = tuple.1;
let z = tuple.2;
println!("x is {}", x);
Как и в случае индексации массивов, индексы начинаются с нуля, но здесь, в
отличие от массивов, используется .
, а не []
.
Вы можете найти больше информации о кортежах (tuple
) в документации к
стандартной библиотеке (англ.).
Функции тоже имеют тип! Это выглядит следующим образом:
fn foo(x: i32) -> i32 { x }
let x: fn(i32) -> i32 = foo;
В данном примере x
— это «указатель на функцию», которая принимает в качестве
аргумента i32
и возвращает i32
.