Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Функции

Функции в коде на Rust встречаются повсюду. Одну из самых важных функций языка вы уже видели — это main, точка входа во многие программы. Видели вы и ключевое слово fn, которым объявляются новые функции.

Имена функций и переменных в Rust принято писать в стиле snake case: все буквы строчные, а слова разделяются подчёркиваниями. Вот программа с примером определения функции:

Файл: src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Функция в Rust определяется так: fn, затем имя функции и круглые скобки. Фигурные скобки показывают компилятору, где начинается и где заканчивается тело функции.

Чтобы вызвать любую определённую нами функцию, достаточно написать её имя и круглые скобки. Функция another_function определена в программе, поэтому её можно вызвать из main. Обратите внимание: в исходном коде мы определили another_function после main, но могли бы определить и до. Rust не важно, в каком порядке идут функции, — важно лишь, чтобы вызывающий код их видел (чтобы функция была в доступной ему области видимости).

Чтобы поэкспериментировать с функциями, создайте новый бинарный проект functions. Поместите пример с another_function в src/main.rs и запустите. Вывод должен быть таким:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

Строки выполняются в том порядке, в каком они записаны в main. Сначала выводится «Hello, world!», затем вызывается another_function и выводит своё сообщение.

Параметры

У функций могут быть параметры — особые переменные, которые являются частью сигнатуры функции. Если у функции есть параметры, при вызове в них можно передать конкретные значения. Строго говоря, сами конкретные значения называются аргументами, но в разговоре слова «параметр» и «аргумент» часто используют как синонимы — и для переменных в определении функции, и для значений, которые передаются при вызове.

В этой версии another_function мы добавили параметр:

Файл: src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

Запустите программу — вывод должен быть таким:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

У another_function один параметр с именем x и типом i32. Когда мы передаём в another_function значение 5, макрос println! подставляет 5 в строку формата на место фигурных скобок с x.

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

Если параметров несколько, их объявления разделяются запятыми:

Файл: src/main.rs

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Здесь создаётся функция print_labeled_measurement с двумя параметрами. Первый называется value и имеет тип i32. Второй — unit_label типа char. Функция выводит текст, в котором есть и value, и unit_label.

Запустим этот код. Замените программу в src/main.rs проекта functions на пример выше и запустите её через cargo run:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

Мы вызвали функцию со значением 5 для value и 'h' для unit_label, и именно эти значения появились в выводе.

Инструкции и выражения

Тело функции состоит из последовательности инструкций, которая может заканчиваться выражением. У функций, которые мы видели до сих пор, такого завершающего выражения не было, но выражения внутри инструкций вам уже встречались. Rust — язык, основанный на выражениях (expression-based), поэтому разница между инструкциями и выражениями здесь важна. В других языках такого разделения нет, так что давайте разберёмся, что такое инструкции и выражения и как их различие сказывается на теле функций.

  • Инструкции (statements) выполняют какое-то действие и не возвращают значения.
  • Выражения (expressions) вычисляются в итоговое значение.

Посмотрим на примеры.

На самом деле мы уже пользовались и инструкциями, и выражениями. Создать переменную и присвоить ей значение с помощью let — это инструкция. В листинге 3-1 let y = 6; — инструкция.

Файл: src/main.rs
fn main() {
    let y = 6;
}
Листинг 3-1: Объявление функции main, содержащей одну инструкцию

Определение функции — тоже инструкция: весь пример выше сам по себе является инструкцией. (А вот вызов функции, как мы скоро увидим, — уже не инструкция.)

Инструкции не возвращают значений. Поэтому инструкцию let нельзя присвоить другой переменной, как пытается сделать следующий код, — получится ошибка:

Файл: src/main.rs

fn main() {
    let x = (let y = 6);
}

Если запустить эту программу, ошибка будет такой:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^
  |
  = note: only supported directly in conditions of `if` and `while` expressions

warning: unnecessary parentheses around assigned value
 --> src/main.rs:2:13
  |
2 |     let x = (let y = 6);
  |             ^         ^
  |
  = note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
help: remove these parentheses
  |
2 -     let x = (let y = 6);
2 +     let x = let y = 6;
  |

warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted

Инструкция let y = 6 не возвращает значения, так что привязывать к x нечего. В этом Rust отличается от других языков, например C и Ruby, где присваивание возвращает присвоенное значение. Там можно написать x = y = 6, и обе переменные, x и y, получат значение 6. В Rust так не получится.

Выражения вычисляются в значение, и из них состоит большая часть остального кода, который вы будете писать на Rust. Возьмём математическую операцию, например 5 + 6: это выражение, которое вычисляется в 11. Выражения могут входить в инструкции: в листинге 3-1 6 в инструкции let y = 6; — это выражение, которое вычисляется в значение 6. Вызов функции — выражение. Вызов макроса — выражение. Новый блок области видимости, созданный фигурными скобками, — тоже выражение. Например:

Файл: src/main.rs

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}

Вот это выражение:

{
    let x = 3;
    x + 1
}

— блок, который в данном случае вычисляется в 4. Это значение и привязывается к y в инструкции let. Обратите внимание на строку x + 1: в отличие от большинства строк, которые вы видели до сих пор, в конце у неё нет точки с запятой. Выражения не заканчиваются точкой с запятой. Если поставить точку с запятой в конце выражения, оно превратится в инструкцию и перестанет возвращать значение. Помните об этом — дальше мы будем разбирать возвращаемые значения функций и выражения.

Функции, возвращающие значения

Функции могут возвращать значения вызвавшему их коду. Возвращаемым значениям мы не даём имён, но их тип нужно объявить после стрелки (->). В Rust возвращаемое значение функции — это значение последнего выражения в её теле. Из функции можно выйти и раньше, с помощью ключевого слова return и значения, но большинство функций неявно возвращают последнее выражение. Вот пример функции, которая возвращает значение:

Файл: src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

В функции five нет ни вызовов функций, ни макросов, ни даже инструкций let — только число 5. И это совершенно нормальная функция на Rust. Заметьте, что тип возвращаемого значения тоже указан: -> i32. Запустите этот код — вывод должен быть таким:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

5 в функции five — это её возвращаемое значение, поэтому тип возвращаемого значения — i32. Разберём подробнее. Здесь важны два момента. Во-первых, строка let x = five(); показывает, что мы инициализируем переменную значением, которое вернула функция. Функция five возвращает 5, так что эта строка равносильна такой:

#![allow(unused)]
fn main() {
let x = 5;
}

Во-вторых, у функции five нет параметров, зато указан тип возвращаемого значения, а её тело — одинокое 5 без точки с запятой. Точки с запятой нет, потому что это выражение, значение которого мы хотим вернуть.

Посмотрим на другой пример:

Файл: src/main.rs

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

    println!("The value of x is: {x}");
}

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

Этот код выведет The value of x is: 6. А что будет, если поставить точку с запятой в конце строки x + 1 и тем самым превратить выражение в инструкцию?

Файл: src/main.rs

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

    println!("The value of x is: {x}");
}

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

Компиляция завершится ошибкой:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: remove this semicolon to return this value

For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` (bin "functions") due to 1 previous error

Главное сообщение, mismatched types («несовпадение типов»), указывает на суть проблемы. Функция plus_one по определению должна вернуть i32, но инструкция ни во что не вычисляется — это обозначается (), единичным типом. Получается, функция ничего не возвращает, а это противоречит её определению — отсюда и ошибка. В выводе Rust даже подсказывает, как, скорее всего, это исправить: убрать точку с запятой. И это действительно устранит ошибку.