Переменные мы уже умеем объявлять: let spent = 0;. Но что именно лежит в такой «коробке»: целое число, дробное, буква? Для денег это важно. А ещё нужно держать вместе несколько трат, а не по переменной на каждую. Разберёмся с тем и другим.
Целые числа: сколько места нужно под число
Представьте тару: стакан, ведро, бочка. Числа в Rust устроены так же: у каждого есть тип — он говорит, сколько места отведено под число и какие значения в него влезают. Имя типа состоит из буквы и числа бит: i32 — целое со знаком (может быть отрицательным), u32 — целое без знака (только ноль и больше).
| Тип | Что влезает | Для чего |
|---|---|---|
u8 | от 0 до 255 | маленькие счётчики |
i32 | примерно от −2,1 млрд до 2,1 млрд | тип по умолчанию |
u32 | от 0 до 4 294 967 295 | наши рубли |
i64 / u64 | до очень больших чисел | когда u32 мало |
usize | зависит от компьютера | длины и номера элементов |
Если тип не указан, Rust сам выбирает i32. Чтобы выбрать другой, после имени пишут двоеточие и тип — это называется аннотацией типа:
let a = 350;
let spent: u32 = 1550;
let big: u64 = 5_000_000_000;
Разбор по строкам
let a = 350;— тип не указан, будетi32.let spent: u32 = 1550;— явно просимu32.let big: u64 = 5_000_000_000;— вu32не влезло бы; подчёркивания для удобства.
Для денег без копеек мы выбираем u32: отрицательных трат у нас нет, а четырёх миллиардов рублей хватит с запасом.
Дробные числа и арифметика
Дробные числа — тип f64 (от английского float, «плавающая точка»). Литерал пишут с точкой: 3.5. Складывать, вычитать и умножать можно как обычно: + - *. С делением есть сюрприз: у целых чисел / отбрасывает дробную часть, а % даёт остаток.
println!("{}", 7 / 2);
println!("{}", 7 % 2);
println!("{}", 7.0 / 2.0);
3
1
3.5
Разбор по строкам
7 / 2— целые числа, ответ целый: 3.7 % 2— остаток: 7 минус 6, будет 1.7.0 / 2.0— дробные числа, ответ 3.5.
Rust не смешивает целые и дробные числа молча. Чтобы превратить одно в другое, пишут as и имя типа. Официально это приведение типов: spent as f64 даёт дробную копию значения. При обратном превращении дробная часть отбрасывается: 3.99 as u32 будет 3.
Переполнение
Коробка бывает слишком мала. У u32 нижняя граница — ноль, и ниже неё опуститься нельзя. Вычтем из 100 число 350: ответ был бы отрицательным — это переполнение (overflow). Очевидные случаи компилятор ловит до запуска (при cargo run и cargo build):
let spent: u32 = 100;
let refund: u32 = 350;
println!("{}", spent - refund);
error: this arithmetic operation will overflow
attempt to compute `100_u32 - 350_u32`, which would overflow
Когда числа станут известны только во время работы (например, их введёт пользователь), ловить будет программа: в обычной сборке cargo run она остановится с паникой — аварийной остановкой с сообщением attempt to subtract with overflow. В сборке cargo build --release проверки нет, и число молча «заворачивается» (0 − 1 станет 4294967295). Поэтому о границах типа нужно думать заранее.
bool и char
Тип bool хранит ровно два значения: true (да) и false (нет), как выключатель. Тип char — одна буква или знак, в одинарных кавычках: 'р'. Он занимает 4 байта, поэтому вмещает любой символ Юникода, и кириллица тут не исключение. Двойные кавычки ("рубли") — уже текст, о нём позже.
let paid = true;
let letter = 'р';
println!("{paid} {letter}");
true р
Разбор по строкам
let paid = true;— этоbool.let letter = 'р';— одинарные кавычки — значитchar.println!("{paid} {letter}");— имена можно писать прямо в скобках.
Кортежи и массивы
Одна трата — это сумма и категория. Чтобы держать их вместе, есть кортеж (tuple): как конверт, в который кладут несколько вещей разных типов. Часть берут по номеру через точку (с нуля) или распаковывают целиком:
let expense = (350, "еда");
println!("{}", expense.0);
let (amount, category) = expense;
println!("{amount} руб., {category}");
println!("{:?}", expense);
350
350 руб., еда
(350, "еда")
Разбор по строкам
let expense = (350, "еда");— кортеж из числа и текста (текст в двойных кавычках).expense.0— элемент с номером 0, то есть первый: 350.let (amount, category) = expense;— имена по порядку получают элементы кортежа.{:?}— отладочная печать; обычное{}для кортежа не работает.
Если нужно много значений одного типа, берут массив: ряд одинаковых ячеек. Длина тоже фиксирована и входит в тип: [u32; 3] — три числа u32.
let expenses: [u32; 3] = [350, 1200, 99];
println!("{}", expenses[1]);
println!("{}", expenses.len());
println!("{:?}", expenses);
1200
3
[350, 1200, 99]
Разбор по строкам
[u32; 3]— тип массива: тип элементов, точка с запятой, длина.expenses[1]— индекс с нуля: это вторая трата.expenses.len()— длина массива.{:?}— массив тоже печатается так.
Выход за границы
У массива из трёх элементов индексы 0, 1 и 2. Обращение expenses[5] — ошибка: очевидный случай компилятор остановит (this operation will panic at runtime), а если индекс известен только при работе, программа упадёт с паникой index out of bounds: the len is 3 but the index is 5.
Практика: трата и массив в копилке
В файле src/main.rs проекта kopilka оставьте всё, что было, и перед закрывающей } функции main добавьте:
let expenses: [u32; 3] = [350, 1200, 99];
let first = expenses[0];
println!("Первая трата: {first} руб.");
println!("Трат: {}", expenses.len());
println!("Все траты: {:?}", expenses);
Запустите cargo run. К знакомым строкам добавится:
Первая трата: 350 руб.
Трат: 3
Все траты: [350, 1200, 99]
Разбор по строкам
let expenses: [u32; 3] = ...— массив пробных трат.let first = expenses[0];— первая трата, индекс 0.expenses.len()— число трат: 3.
Проверьте себя:
- Что произойдёт, если заменить
expenses[0]наexpenses[3]? Запустите и прочитайте сообщение. - Уберите
:?из последнегоprintln!. Какую ошибку покажет компилятор? - Замените
[u32; 3]на[u32; 4]. Что скажет компилятор и почему? - Почему для
MONTH_LIMITи трат мы выбралиu32, а неi32?
Частые ошибки
1. Печать массива через {}
Видно: error[E0277]: `[{integer}; 3]` doesn't implement `std::fmt::Display`. Причина: у массива нет обычной печати. Исправление: println!("{:?}", expenses);.
2. Целое и дробное вперемешку
Видно: error[E0277]: cannot multiply `u32` by `f64`. Причина: типы Rust сам не смешивает. Исправление: spent as f64 * 2.5.
3. Индекс за границей
Видно: this operation will panic at runtime или паника index out of bounds. Причина: индексы идут с нуля, последний — len() - 1. Исправление: проверьте номер.
Что важно запомнить
- Без аннотации целое число —
i32, дробное —f64; деньги в рублях держим вu32. 7 / 2даёт 3,%— остаток,asменяет тип.- Выход за границы типа — переполнение (паника в debug, заворачивание в release).
bool—true/false,char— один символ в одинарных кавычках.- Кортеж: разные типы,
.0или распаковка. Массив: один тип, фиксированная длина, индекс с нуля. - Кортеж и массив печатаются через
{:?}.
Подробнее — в главе о типах данных книги Rust.
Дальше — практикум: соберём из всего изученного первую настоящую копилку.