$ sudo teach IT
Модуль 1 · Первые шаги · Урок 1.5

Практикум: первая копилка

Вы соберёте программу целиком: лимит, массив трат, сумма, остаток и процент от лимита — с ровным выводом.

Практикум~30 минутНовичок

Вы уже умеете создать проект, печатать текст, заводить переменные и константы и хранить несколько трат в массиве. Но пока это были отдельные кирпичики. Сегодня сложим из них первую настоящую версию «Копилки», которая сама посчитает, сколько потрачено, сколько осталось и какую долю лимита вы уже использовали.

Что должна напечатать программа

Сначала представим, что программа должна показать, как чертёж перед стройкой. Вот наш чертёж для трёх пробных трат — 350, 1200 и 99 рублей:

Вывод
Копилка 0.1
Учёт расходов за месяц

Лимит месяца:   30000 руб.
Потрачено:       1649 руб.
Осталось:       28351 руб.
Использовано лимита: 5%
Точнее: 5.5%

Шапка из двух строк у нас уже есть с первого урока про Cargo. Новое — четыре числа: сумма трат, остаток, целый процент и процент с одним знаком после запятой. Идём по шагам. Работаем в проекте kopilka, файл src/main.rs.

Шаг 1. Данные: лимит и траты

Лимит месяца не меняется во время работы, поэтому он живёт в константе над main. Траты пока записываем прямо в код: это пробные числа, ввод с клавиатуры появится позже.

Rust · main.rs
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. Считаем сумму и остаток

Циклов мы ещё не проходили, поэтому складываем траты вручную, по индексам. Громоздко, но позже эту строчку заменит короткая функция.

Rust · main.rs
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, и целая часть частного уже что-то значит.

Rust · main.rs
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.

Rust · main.rs
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 внутри текста.
Rust · main.rs
println!("Потрачено:     {:>6} руб.", spent);
println!("Точнее: {:.1}%", percent_exact);

Разбор по строкам

  • {:>6} — вместо числа 1649 на экране окажутся четыре символа и два пробела слева, то есть два пробела и 1649. Все числа в столбце встанут ровно.
  • {:.1} — число 5,4966… превратится в 5.5 — Rust печатает дробную часть через точку.
  • % — в обычном тексте println! знак процента не особенный, пишем его как есть.

Практика: собираем main.rs

  1. Откройте терминал в папке проекта kopilka (там лежит Cargo.toml).
  2. Замените всё содержимое src/main.rs кодом ниже.
  3. Выполните cargo run.
Rust · main.rs
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 не бывает отрицательным: вычитать больше, чем есть, нельзя.

Следующий шаг — функции: расчёт суммы и статуса мы вынесем в отдельные блоки с именами, чтобы не повторять один и тот же код.

На следующий урок