Наша «Копилка» пока умеет только печатать две строки. Чтобы считать траты, ей нужно запоминать числа: лимит месяца, сколько потрачено на данный момент. Для этого в любом языке есть переменные, а в Rust у них есть особенность, которая сначала удивляет, а потом начинает нравиться: по умолчанию их нельзя менять.
let: коробка с наклейкой
Представьте коробку, на которую приклеена бумажка с названием, например «лимит». Внутрь положили число. Когда вам нужно число, вы не вспоминаете его, а говорите: «возьми то, что в коробке „лимит“». Переменная работает так же: это имя, под которым в памяти лежит значение.
Создаётся переменная ключевым словом let (по-английски «пусть»): «пусть limit будет равно 30000». Само действие называется объявлением переменной, а знак = кладёт значение в коробку. Чтобы напечатать содержимое, в строке пишут фигурные скобки: либо пустые {} и значение после запятой, либо имя прямо внутри, как {limit}.
fn main() {
let limit = 30000;
println!("Лимит: {}", limit);
println!("Лимит: {limit}");
}
Разбор по строкам
let limit = 30000;— объявляем переменнуюlimitи кладём в неё число 30000. Точка с запятой в конце, как всегда.println!("Лимит: {}", limit);— пустые скобки{}— место для значения; самlimitстоит после запятой.println!("Лимит: {limit}");— то же самое короче: имя переменной прямо внутри скобок. В курсе мы будем писать именно так.
Лимит: 30000
Лимит: 30000
Почему значение нельзя поменять
Попробуем то, что в других языках делают не задумываясь: положим в «потрачено» ноль, а потом прибавим первую трату.
fn main() {
let spent = 0;
spent = spent + 350;
println!("{spent}");
}
Компилятор остановится и скажет:
error[E0384]: cannot assign twice to immutable variable `spent`
--> src/main.rs:3:5
|
2 | let spent = 0;
| ----- first assignment to `spent`
3 | spent = spent + 350;
| ^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut spent = 0;
| +++
Читаем по-человечески: «нельзя присвоить дважды неизменяемой переменной spent». Первое значение она получила в строке 2, а в строке 3 мы попытались положить второе. Внизу компилятор сам подсказывает выход: добавить mut.
Такое свойство называется неизменяемостью по умолчанию (immutability): переменная, объявленная через let, после первого значения только читается. Это не придирка. Когда вы видите в большой программе let limit = 30000;, вы твёрдо знаете, что дальше limit везде равен 30000, и не нужно проверять, не изменил ли его кто-то по дороге. Меняться разрешено только тому, что вы сами пометили.
mut: разрешаем менять
Пометка называется mut (от mutable, «изменяемый»). Пишется она сразу после let. Теперь коробку можно открывать и класть в неё новое значение.
fn main() {
let mut spent = 0;
println!("Потрачено: {spent}");
spent = spent + 350;
println!("Потрачено: {spent}");
spent += 1200;
println!("Потрачено: {spent}");
}
Разбор по строкам
let mut spent = 0;—spentобъявлена изменяемой, начальное значение 0.spent = spent + 350;— берём старое значение, прибавляем 350 и кладём результат обратно. Справа от=всегда считается первым.spent += 1200;— сокращённая запись той же идеи: «прибавить кspent1200». Есть и-=,*=, но нам пока хватит+=.
Потрачено: 0
Потрачено: 350
Потрачено: 1550
Менять можно значение, но не вид данных
Изменяемая переменная остаётся числовой коробкой. Положить в spent текст не получится: компилятор сообщит error[E0308]: mismatched types. Если нужна коробка другого типа, пригодится затенение, о нём чуть ниже.
const: константа на всю программу
Есть значения, которые не меняются никогда: лимит месяца, число дней в неделе, ставка налога. Для них в Rust есть константы. Это не просто неизменяемая переменная, а значение, известное ещё до запуска программы и прописанное в ней навсегда.
const MONTH_LIMIT: u32 = 30_000;
fn main() {
println!("Лимит месяца: {MONTH_LIMIT} руб.");
}
Разбор по строкам
const MONTH_LIMIT: u32 = 30_000;— ключевое словоconst, имя, двоеточие, тип и значение.u32— целое число без минуса; типы мы разберём в следующем уроке.MONTH_LIMIT— имя константы принято писать ЗАГЛАВНЫМИ буквами через подчёркивание, чтобы отличать от обычных переменных.30_000— подчёркивания внутри числа ничего не значат и нужны для читаемости, как пробел в «30 000».const— ставится надmain, снаружи: тогда константу видно во всей программе.
let | let mut | const | |
|---|---|---|---|
| Можно менять | нет | да | нет, никогда |
| Тип указывать | не обязательно | не обязательно | обязательно |
| Где объявлять | внутри функции | внутри функции | где угодно, обычно сверху |
Затенение: то же имя, новая коробка
Есть ещё один способ «поменять» значение, и он вовсе не про mut. Если написать let с уже занятым именем, Rust заводит новую переменную, а старая становится недоступной: новая её «затеняет». По-английски — shadowing.
fn main() {
let spent = 350;
let spent = spent + 1200;
println!("{spent}");
let spent = "много";
println!("{spent}");
}
Разбор по строкам
let spent = 350;— первая коробка с числом.let spent = spent + 1200;— справа ещё видна стараяspent, из неё берём 350 и прибавляем; слева создаётся новая, которая теперь и называетсяspent.let spent = "много";— новая коробка с текстом. Для затенения это допустимо, потому что это новая переменная, а не изменение старой.
1550
много
Затенение и mut — не одно и то же
С mut коробка одна, и тип в ней неизменен. При затенении каждый раз новая коробка, и тип может быть любым. Зато слово let перед каждым шагом обязательно: без него получится обычное присваивание и ошибка E0384.
Практика: лимит и траты в «Копилке»
Продолжаем проект kopilka, который создали в прошлом уроке. Откройте папку проекта в редакторе и замените содержимое src/main.rs целиком:
const MONTH_LIMIT: u32 = 30_000;
fn main() {
println!("Копилка 0.1");
println!("Учёт расходов за месяц");
let mut spent = 0;
spent += 350;
spent += 1200;
println!("Потрачено: {spent} руб. из {MONTH_LIMIT} руб.");
}
Что изменилось: над main появилась константа с лимитом, а внутри — изменяемая переменная spent, в которую мы записали две пробные траты. Запустите программу из папки kopilka:
cargo run
Должно получиться:
Копилка 0.1
Учёт расходов за месяц
Потрачено: 1550 руб. из 30000 руб.
Теперь поэкспериментируйте. Это быстрее любого объяснения.
- Удалите слово
mutи запуститеcargo run: вы должны увидеть уже знакомуюerror[E0384]со стрелкой на строкуspent += 350;. Вернитеmut. - Уберите
: u32из константы: компилятор напишетmissing type for `const` item. Верните тип. - Добавьте строку
let bonus = 5;и не используйте её: появится предупреждениеunused variable, но программа запустится. Удалите строку.
Проверьте себя
Вы справились, если можете ответить: 1) почему без mut строка spent += 350; не собирается; 2) чем const отличается от let; 3) что изменится в выводе, если поменять 30_000 на 25_000; 4) что делает вторая строка let spent = spent + 1;: меняет коробку или заводит новую.
Частые ошибки
1. Забыли mut. Видно: error[E0384]: cannot assign twice to immutable variable. Причина: переменная объявлена через let без mut, а вы её меняете. Исправление: let mut spent = 0;, как подсказывает строка help: в самой ошибке.
2. const без типа. Видно: error: missing type for `const` item и подсказка добавить тип. Причина: у констант тип обязателен, Rust его не выводит. Исправление: const MONTH_LIMIT: u32 = 30_000;.
3. Предупреждение unused variable. Видно: warning: unused variable: `bonus`. Это не ошибка, программа всё равно соберётся: просто вы объявили переменную и ни разу ею не воспользовались. Исправление: удалить её или, если она нужна «на потом», начать имя с подчёркивания — _bonus, и предупреждение пропадёт.
4. Присваивание вместо затенения. Хотели положить в переменную текст, а написали spent = "много"; без let. Видно: error[E0308]: mismatched types. Исправление: либо let spent = "много"; (новая коробка), либо отдельное имя.
Что важно запомнить
let имя = значение;создаёт переменную; по умолчанию она неизменяема.- Попытка изменить такую переменную даёт
error[E0384]; лечится словомmut:let mut spent = 0;. +=прибавляет к изменяемой переменной:spent += 350;.const ИМЯ: тип = значение;— константа: тип обязателен, имя ЗАГЛАВНЫМИ, менять нельзя никогда, ставится надmain.- Затенение (
let x = x + 1;) создаёт новую переменную с тем же именем и позволяет сменить тип. {имя}внутри строкиprintln!подставляет значение переменной.
Подробнее — в главе Variables and Mutability официальной книги.
Дальше: в spent лежит число, но какие вообще бывают числа, текст и логические значения, и как положить в одну переменную сразу несколько трат? Об этом — в уроке про типы данных.