Связывание имён

Любая реальная программа на Rust посложнее, чем «Hello World», использует связывание имён. Это выглядит так:

fn main() { let x = 5; }
fn main() {
    let x = 5;
}

Все операции, производимые ниже, будут происходить в функции main(), так как каждый раз вставлять в примеры fn main() { немного утомляет. Убедитесь, что примеры, приведённые в этом разделе, вы вводите в функцию main(), иначе можете получить ошибку при компиляции.

Во многих языках программирования это называется переменной. Но у связывания переменных в Rust есть пара трюков в рукаве. В левой части выражения let располагается не просто имя переменной, а "шаблон". Это значит, что мы можем делать вещи вроде этой:

fn main() { let (x, y) = (1, 2); }
let (x, y) = (1, 2);

После завершения этого выражения x будет единицей, a y — двойкой. Шаблоны очень мощны, и о них написана отдельная глава. Но на данный момент нам не нужны эти возможности, так что мы просто будем помнить о них и пойдём дальше.

Rust — статически типизированный язык программирования, и значит мы должны указывать типы, и они будут проверяться во время компиляции. Так почему же наш первый пример скомпилировался? В Rust есть нечто, называемое выводом типов. Если Rust самостоятельно может понять, какой тип у переменной, то он не требует указывать его.

Тем не менее, мы можем указать желаемый тип. Он следует после двоеточия (:):

fn main() { let x: i32 = 5; }
let x: i32 = 5;

Если бы мы попросили вас прочитать это вслух, вы бы сказали «x - это связывание типа int со значением пять».

В этом случае мы указали, что x у нас будет 32-битным целым числом со знаком. В Rust есть и другие целочисленные типы. Их имена начинаются с i для целых чисел со знаком и с u для целых чисел без знака. Целые числа могут иметь размер 8, 16, 32 и 64 бита.

В дальнейших примерах мы будем указывать тип в комментариях. Это будет выглядеть вот так:

fn main() { let x = 5; // x: i32 }
fn main() {
    let x = 5; // x: i32
}

Обратите внимание на сходство между этим комментарием и синтаксисом, который вы используете с let. Включение такого типа комментариев не является идиоматичным для Rust, но иногда мы будем включать их для того, чтобы помочь вам понять, какие типы будут выведены Rust.

По умолчанию, связывание неизменяемо. Этот код не скомпилируется:

fn main() { let x = 5; x = 10; }
let x = 5;
x = 10;

И вы получите ошибку:

error: re-assignment of immutable variable `x`
     x = 10;
     ^~~~~~~

Если вы хотите, чтобы связывание было изменяемым, вы можете использовать модификатор mut:

fn main() { let mut x = 5; // mut x: i32 x = 10; }
let mut x = 5; // mut x: i32
x = 10;

Может показаться, что незачем делать связывание неизменяемым по умолчанию. Но вспомните, на чём в первую очередь фокусируется Rust: на безопасности. Если вы случайно забыли указать mut и изменили связывание, компилятор заметит это, и сообщит вам, что вы попытались изменить не то, что собирались. Если бы по умолчанию связывание было изменяемым, то в приведённой выше ситуации компилятор не сможет вам помочь. Если вы намерены изменить значение переменной, то просто добавьте mut.

Есть и другие весомые аргументы в пользу того, чтобы по возможности избегать изменяемого состояния, но это выходит за рамки данной книги. В общем, зачастую вы можете избежать явных изменений, и это предпочтительнее в Rust. Тем не менее, иногда без изменения значения просто не обойтись, так что это не запрещено.

Вернёмся к связыванию. Связывание переменных в Rust имеет ещё одно отличие от других языков: оно требует инициализации перед использованием.

Давайте приступим к рассмотрению вышесказанного. Измените ваш файл src/main.rs так, что бы он выглядел следующим образом:

fn main() { let x: i32; println!("Hello world!"); }
fn main() {
    let x: i32;

    println!("Hello world!");
}

Используйте команду cargo build в командной строке, чтобы собрать проект. Вы должны получить предупреждение, но программа будет работать и будет выводить строку «Привет, мир!»:

   Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variable)]
   on by default
src/main.rs:2     let x: i32;
                      ^

Rust предупредит нас о том, что мы не используем связанную переменную, но от того, что мы её не используем, не будет никакого вреда, поэтому это не ошибка. Однако, всё изменится, если мы попробуем использовать x. Сделаем это. Измените вашу программу так, что бы она выглядела следующим образом:

fn main() { let x: i32; println!("x имеет значение {}", x); }
fn main() {
    let x: i32;

    println!("x имеет значение {}", x);
}

И попробуйте собрать проект. Вы получите ошибку:

$ cargo build
   Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x`
src/main.rs:4     println!("x имеет значение {}", x);
                                                    ^
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
src/main.rs:4:5: 4:42 note: expansion site
error: aborting due to previous error
Could not compile `hello_world`.

Rust не позволит использовать неинициализированную переменную. Далее, поговорим о {}, которые мы добавили в println!.

Если вы добавите две фигурные скобки ({}, иногда называемые «усами»...) в вашу печатаемую строку, Rust истолкует это как просьбу вставки некоторого значения. Строковая интерполяция — это термин в информатике, который обозначает «вставить посреди строки». Мы добавили запятую, и затем x, чтобы указать, что мы хотим вставить x в строку. Запятая используется для разделения параметров, если в функцию или макрос передаётся больше одного параметра.

При вставке переменной в строку, Rust проверит её тип и попытается отобразить осмысленное значение. Если вы хотите указать формат более детально, то можете ознакомиться с доступными способами форматирования строк (англ.). На данный момент мы просто используем способ по умолчанию: печатать целые числа не очень сложно.