1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Функции

Каждая программа на Rust имеет по крайней мере одну функцию — main:

fn main() {
}Run

Это простейшее объявление функции. Как мы упоминали ранее, ключевое слово fn объявляет функцию. За ним следует её имя, пустые круглые скобки (поскольку эта функция не принимает аргументов), а затем тело функции, заключённое в фигурные скобки. Вот функция foo:

fn foo() {
}Run

Итак, что насчёт аргументов, принимаемых функцией? Вот функция, печатающая число:

fn print_number(x: i32) {
    println!("x равен: {}", x);
}Run

Вот полная программа, использующая функцию print_number:

fn main() {
    print_number(5);
}

fn print_number(x: i32) {
    println!("x равен: {}", x);
}Run

Как видите, аргументы функций похожи на операторы let: вы можете объявить тип аргумента после двоеточия.

Вот полная программа, которая складывает два числа и печатает их:

fn main() {
    print_sum(5, 6);
}

fn print_sum(x: i32, y: i32) {
    println!("сумма чисел: {}", x + y);
}Run

Аргументы разделяются запятой — и при вызове функции, и при её объявлении.

В отличие от let, вы должны объявлять типы аргументов функции. Этот код не скомпилируется:

fn print_sum(x, y) {
    println!("сумма чисел: {}", x + y);
}Run

Вы увидите такую ошибку:

expected one of `!`, `:`, or `@`, found `)`
fn print_number(x, y) {

Это осознанное решение при проектировании языка. Бесспорно, вывод типов во всей программе возможен. Однако даже в Haskell считается хорошим стилем явно документировать типы функций, хотя в этом языке и возможен полный вывод типов. Мы считаем, что принудительное объявление типов функций при сохранении локального вывода типов — это хороший компромисс.

Как насчёт возвращаемого значения? Вот функция, которая прибавляет один к целому:

fn add_one(x: i32) -> i32 {
    x + 1
}Run

Функции в Rust возвращают ровно одно значение, тип которого объявляется после «стрелки». «Стрелка» представляет собой дефис (-), за которым следует знак «больше» (>). Заметьте, что в функции выше нет точки с запятой. Если бы мы добавили её:

fn add_one(x: i32) -> i32 {
    x + 1;
}Run

мы бы получили ошибку:

error: not all control paths return a value
fn add_one(x: i32) -> i32 {
     x + 1;
}

help: consider removing this semicolon:
     x + 1;
          ^

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

Выражения и операторы

Rust — в первую очередь язык, ориентированный на выражения. Есть только два типа операторов, а всё остальное является выражением.

А в чём же разница? Выражение возвращает значение, в то время как оператор - нет. Вот почему мы получаем здесь «not all control paths return a value»: оператор х + 1; не возвращает значение. Есть два типа операторов в Rust: «операторы объявления» и «операторы выражения». Все остальное — выражения. Давайте сначала поговорим об операторах объявления.

Оператор объявления — это связывание. В некоторых языках связывание переменных может быть записано как выражение, а не только как оператор. Например, в Ruby:

x = y = 5

Однако, в Rust использование let для связывания не является выражением. Следующий код вызовет ошибку компиляции:

let x = (let y = 5); // expected identifier, found keyword `let`Run

Здесь компилятор сообщил нам, что ожидал увидеть выражение, но let является оператором, а не выражением.

Обратите внимание, что присвоение уже связанной переменной (например: y = 5) является выражением, но его значение не особенно полезно. В отличие от других языков, где результатом присваивания является присваиваемое значение (например, 5 из предыдущего примера), в Rust значением присваивания является пустой кортеж ().

let mut y = 5;

let x = (y = 6);  // x будет присвоено значение `()`, а не `6`Run

Вторым типом операторов в Rust является оператор выражения. Его цель - превратить любое выражение в оператор. В практическом плане, грамматика Rust ожидает, что за операторами будут идти другие операторы. Это означает, что вы используете точку с запятой для отделения выражений друг от друга. Rust выглядит как многие другие языки, которые требуют использовать точку с запятой в конце каждой строки. Вы увидите её в конце почти каждой строки кода на Rust.

Из-за чего мы говорим «почти»? Вы это уже видели в этом примере:

fn add_one(x: i32) -> i32 {
    x + 1
}Run

Наша функция объявлена как возвращающая i32. Но если в конце есть точка с запятой, то вместо этого функция вернёт (). Компилятор Rust обрабатывает эту ситуацию и предлагает удалить точку с запятой.

Досрочный возврат из функции

А что насчёт досрочного возврата из функции? У нас есть для этого ключевое слово return:

fn foo(x: i32) -> i32 {
    return x;

    // дальнейший код не будет исполнен!
    x + 1
}Run

return можно написать в последней строке тела функции, но это считается плохим стилем:

fn foo(x: i32) -> i32 {
    return x + 1;
}Run

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

Расходящиеся функции

Для функций, которые не возвращают управление («расходящихся»), в Rust есть специальный синтаксис:

fn diverges() -> ! {
    panic!("Эта функция не возвращает управление!");
}Run

panic! — это макрос, как и println!(), который мы встречали ранее. В отличие от println!(), panic!() вызывает остановку текущего потока исполнения с заданным сообщением. Поскольку эта функция вызывает остановку исполнения, она никогда не вернёт управление. Поэтому тип её возвращаемого значения обозначается знаком ! и читается как «расходится».

Если добавить функцию diverges() и запустить её, то вы получите следующее сообщение:

thread ‘<main>’ panicked at ‘Эта функция не возвращает управление!’, hello.rs:2

Для получение более подробной информации вы можете посмотреть трассировку установив переменную среды RUST_BACKTRACE:

$ RUST_BACKTRACE=1 ./diverges
thread '<main>' panicked at 'Эта функция не возвращает управление!', hello.rs:2
stack backtrace:
   1:     0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
   2:     0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
   3:     0x7f402773960e - rt::unwind::begin_unwind_inner::h2844b8c5e81e79558Bw
   4:     0x7f4027738893 - rt::unwind::begin_unwind::h4375279447423903650
   5:     0x7f4027738809 - diverges::h2266b4c4b850236beaa
   6:     0x7f40277389e5 - main::h19bb1149c2f00ecfBaa
   7:     0x7f402773f514 - rt::unwind::try::try_fn::h13186883479104382231
   8:     0x7f402773d1d8 - __rust_try
   9:     0x7f402773f201 - rt::lang_start::ha172a3ce74bb453aK5w
  10:     0x7f4027738a19 - main
  11:     0x7f402694ab44 - __libc_start_main
  12:     0x7f40277386c8 - <unknown>
  13:                0x0 - <unknown>

RUST_BACKTRACE также работает при выполнении команды run:

$ RUST_BACKTRACE=1 cargo run
     Running `target/debug/diverges`
thread '<main>' panicked at 'Эта функция не возвращает управление!', hello.rs:2
stack backtrace:
   1:     0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
   2:     0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
   3:     0x7f402773960e - rt::unwind::begin_unwind_inner::h2844b8c5e81e79558Bw
   4:     0x7f4027738893 - rt::unwind::begin_unwind::h4375279447423903650
   5:     0x7f4027738809 - diverges::h2266b4c4b850236beaa
   6:     0x7f40277389e5 - main::h19bb1149c2f00ecfBaa
   7:     0x7f402773f514 - rt::unwind::try::try_fn::h13186883479104382231
   8:     0x7f402773d1d8 - __rust_try
   9:     0x7f402773f201 - rt::lang_start::ha172a3ce74bb453aK5w
  10:     0x7f4027738a19 - main
  11:     0x7f402694ab44 - __libc_start_main
  12:     0x7f40277386c8 - <unknown>
  13:                0x0 - <unknown>

Значение расходящейся функции может быть использовано как значение любого типа:

let x: i32 = diverges();
let x: String = diverges();Run

Указатели на функции

Можно объявить имя, связанное с функцией:

let f: fn(i32) -> i32;Run

f — это имя, связанное с указателем на функцию, которая принимает в качестве аргумента i32 и возвращает i32. Например:

fn plus_one(i: i32) -> i32 {
    i + 1
}

// без вывода типа
let f: fn(i32) -> i32 = plus_one;

// с выводом типа
let f = plus_one;Run

Теперь мы можем использовать f, чтобы вызвать функцию:

let six = f(5);Run