Вы уже умеете создать проект, печатать текст, заводить переменные и константы и хранить несколько трат в массиве. Но пока это были отдельные кирпичики. Сегодня сложим из них первую настоящую версию «Копилки», которая сама посчитает, сколько потрачено, сколько осталось и какую долю лимита вы уже использовали.
Что должна напечатать программа
Сначала представим, что программа должна показать, как чертёж перед стройкой. Вот наш чертёж для трёх пробных трат — 350, 1200 и 99 рублей:
Копилка 0.1
Учёт расходов за месяц
Лимит месяца: 30000 руб.
Потрачено: 1649 руб.
Осталось: 28351 руб.
Использовано лимита: 5%
Точнее: 5.5%
Шапка из двух строк у нас уже есть с первого урока про Cargo. Новое — четыре числа: сумма трат, остаток, целый процент и процент с одним знаком после запятой. Идём по шагам. Работаем в проекте kopilka, файл src/main.rs.
Шаг 1. Данные: лимит и траты
Лимит месяца не меняется во время работы, поэтому он живёт в константе над main. Траты пока записываем прямо в код: это пробные числа, ввод с клавиатуры появится позже.
const MONTH_LIMIT: u32 = 30_000;
fn main() {
let expenses = [350, 1200, 99];
}
Разбор по строкам
const MONTH_LIMIT: u32 = 30_000;— константа с обязательным типомu32(целое без знака: деньги без минуса); символ_просто разделяет разряды.let expenses = [350, 1200, 99];— массив из трёх чисел. Тип[u32; 3]Rust определит сам: дальше мы будем вычитать эти траты изMONTH_LIMIT, а онu32.
Откуда Rust знает тип
Если число нигде не подписано, компилятор смотрит, как оно используется дальше, и подбирает тип по соседям. Это называется выводом типов. Захотите быть явным — напишите let expenses: [u32; 3] = [350, 1200, 99];.
Шаг 2. Считаем сумму и остаток
Циклов мы ещё не проходили, поэтому складываем траты вручную, по индексам. Громоздко, но позже эту строчку заменит короткая функция.
let spent = expenses[0] + expenses[1] + expenses[2];
let left = MONTH_LIMIT - spent;
Разбор по строкам
expenses[0] + expenses[1] + expenses[2]— индексы в массиве начинаются с нуля, поэтому три элемента — это[0],[1]и[2]. Получится 350 + 1200 + 99 = 1649.MONTH_LIMIT - spent— остаток: 30000 - 1649 = 28351. Оба числаu32, так что и результатu32.
Шаг 3. Процент: сначала умножаем
Процент от лимита — это доля потраченного, умноженная на сто. Самая очевидная запись «потрачено делим на лимит и умножаем на 100» подводит именно в целых числах. Представьте, что вы делите 1649 яблок на 30000 корзин: в каждую достанется меньше одного яблока, а целые числа дробей не хранят — они отбрасывают всё после запятой. Получается 0, а 0 умножить на 100 остаётся нулём.
Выход простой: сначала умножить, потом делить. Тогда делится большое число 164900, и целая часть частного уже что-то значит.
let wrong = spent / MONTH_LIMIT * 100;
let percent = spent * 100 / MONTH_LIMIT;
Разбор по строкам
spent / MONTH_LIMIT * 100— 1649 / 30000 даёт 0, дальше 0 * 100 — тоже 0. Неверно.spent * 100 / MONTH_LIMIT— 1649 * 100 = 164900, потом 164900 / 30000 = 5 (целая часть от 5,49). Верно: потрачено около 5 процентов.
Если нужна дробная часть, переходим на дробные числа. Мы уже знаем приведение as: оно превращает целое в f64.
let percent_exact = spent as f64 * 100.0 / MONTH_LIMIT as f64;
Разбор по строкам
spent as f64— то же число, но дробного типа. Целое и дробное Rust сам не смешивает, поэтому приводим явно.* 100.0 /— сто теперь тоже дробное, с точкой:100.0. Результат — 5,4966…MONTH_LIMIT as f64— второе число тоже приводим кf64, иначе компилятор откажется делить дробное на целое.
Шаг 4. Красивый вывод
Числа в колонке читаются легче, когда они выровнены по правому краю: единицы под единицами. Для этого в фигурных скобках println! можно написать спецификатор формата — короткую инструкцию, как показать значение. Бытовая аналогия — бланк с клеточками: каждая цифра занимает свою клетку, а лишние клетки слева остаются пустыми.
{:>6}— поле шириной 6 символов, значение прижато вправо;{:.1}— для дробного числа ровно один знак после запятой (с округлением);println!()без текста — пустая строка; то же самое делает\nвнутри текста.
println!("Потрачено: {:>6} руб.", spent);
println!("Точнее: {:.1}%", percent_exact);
Разбор по строкам
{:>6}— вместо числа 1649 на экране окажутся четыре символа и два пробела слева, то есть два пробела и1649. Все числа в столбце встанут ровно.{:.1}— число 5,4966… превратится в5.5— Rust печатает дробную часть через точку.%— в обычном текстеprintln!знак процента не особенный, пишем его как есть.
Практика: собираем main.rs
- Откройте терминал в папке проекта
kopilka(там лежитCargo.toml). - Замените всё содержимое
src/main.rsкодом ниже. - Выполните
cargo run.
const MONTH_LIMIT: u32 = 30_000;
fn main() {
println!("Копилка 0.1");
println!("Учёт расходов за месяц");
println!();
let expenses = [350, 1200, 99];
let spent = expenses[0] + expenses[1] + expenses[2];
let left = MONTH_LIMIT - spent;
let percent = spent * 100 / MONTH_LIMIT;
let percent_exact = spent as f64 * 100.0 / MONTH_LIMIT as f64;
println!("Лимит месяца: {:>6} руб.", MONTH_LIMIT);
println!("Потрачено: {:>6} руб.", spent);
println!("Осталось: {:>6} руб.", left);
println!("Использовано лимита: {}%", percent);
println!("Точнее: {:.1}%", percent_exact);
}
Разбор по строкам
const MONTH_LIMIT— лимит надmain, как и раньше.println!();— пустая строка между шапкой и расчётом.let spent— сумма трат, посчитанная вручную через индексы.let left— остаток лимита.let percent— целый процент: сначала умножаем, потом делим.let percent_exact— тот же процент, но дробный, черезas f64.println!(... {:>6} ...)— три числа, выровненные по правому краю. Пробелы в тексте перед скобками подобраны так, чтобы столбец числа совпадал.
В терминале должно получиться:
Копилка 0.1
Учёт расходов за месяц
Лимит месяца: 30000 руб.
Потрачено: 1649 руб.
Осталось: 28351 руб.
Использовано лимита: 5%
Точнее: 5.5%
Проверьте себя
- Измените вторую трату на 2000 и запустите снова. Сколько теперь «Потрачено» и «Осталось»? (Должно получиться 2449 и 27551.)
- Напечатайте весь массив одной строкой:
println!("{:?}", expenses);. Что получилось? - Поменяйте местами в процентах деление и умножение. Какой процент напечатается и почему?
- Замените
{:.1}на{:.3}и посмотрите на результат.
Частые ошибки
Процент всегда равен нулю
Что видно: вместо 5% программа печатает 0%. Почему: вы написали spent / MONTH_LIMIT * 100, а целое деление 1649 на 30000 отбрасывает дробь. Как исправить: сначала умножить — spent * 100 / MONTH_LIMIT.
Потрачено больше лимита
Что видно: измените траты на [20_000, 10_000, 99] — сумма станет больше лимита, и вычитание MONTH_LIMIT - spent не получится. Почему: u32 не умеет быть отрицательным. Здесь все числа известны заранее, поэтому компилятор замечает беду сам (при cargo run или cargo build; одна лишь проверка cargo check этот случай пропускает):
error: this arithmetic operation will overflow
--> src/main.rs:8:16
|
8 | let left = MONTH_LIMIT - spent;
| ^^^^^^^^^^^^^^^^^^^ attempt to compute `30000_u32 - 30099_u32`, which would overflow
|
= note: `#[deny(arithmetic_overflow)]` on by default
Если бы сумма приходила с клавиатуры, компилятор заранее не узнал бы о проблеме. Тогда в обычной сборке программа остановилась бы с паникой attempt to subtract with overflow, а в сборке --release число молча «завернулось» бы в огромное значение. Как правильно защищаться от таких случаев, разберём во втором модуле, в уроке про условия.
Смешали целое и дробное
Что видно: ошибка error[E0277]: cannot divide `f64` by `u32`. Почему: Rust не приводит типы сам. Как исправить: привести оба числа через as f64 и писать 100.0 вместо 100.
Что важно запомнить
- Сначала представьте результат (вывод целиком), потом пишите код.
- Индексы массива начинаются с нуля:
expenses[0]— первая трата. - Целое деление отбрасывает дробь, поэтому в процентах сначала умножаем, потом делим.
- Для дробного результата приводим числа через
as f64и пишем100.0с точкой. {:>6}выравнивает по правому краю,{:.1}оставляет один знак после запятой.u32не бывает отрицательным: вычитать больше, чем есть, нельзя.
Следующий шаг — функции: расчёт суммы и статуса мы вынесем в отдельные блоки с именами, чтобы не повторять один и тот же код.