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!("Другая функция.");
}

Функция в 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!
Другая функция.

Выражения исполняются в том порядке, в каком они записаны в функции main. Первым печатается сообщение “Hello, world!”, и только потом вызывается another_function и печатается её сообщение.

Параметры

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

Добавим функции another_function параметр:

Файл: src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("Значение x: {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`
Значение x: 5

Функция another_function имеет один параметр под названием x. Тип x определён как i32. Когда мы вызываем функцию another_function с аргументом 5, макрос println! принимает переменную x нашей функции и помещает вместо неё её значение 5 на место метки подстановки.

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rust’s design: Requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what type you mean. The compiler is also able to give more-helpful error messages if it knows what types the function expects.

Чтобы определить функцию с несколькими параметрами, разделите их запятыми; вот так:

Файл: src/main.rs

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

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("Физическая величина: {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`
Физическая величина: 5ч

Поскольку мы вызвали функцию с аргументами 5 и 'ч' (соответственно параметрам value и unit_label), вывод программы содержит эти значения.

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

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

  • Statements are instructions that perform some action and do not return a value.
  • Expressions evaluate to a resultant value.

Let’s look at some examples.

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

Filename: src/main.rs
fn main() {
    let y = 6;
}
Listing 3-1: A main function declaration containing one statement

Function definitions are also statements; the entire preceding example is a statement in itself. (As we’ll see shortly, calling a function is not a statement, though.)

Инструкции не возвращают значений. Следовательно, вы не можете присвоить инструкцию 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)]` 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 не с чем связывать. Это отличается от того, что обычно происходит в других языках, вроде C или Ruby: в них присвоение значения возвращает присвоенное значение. В таких языках возможны конструкции вроде x = y = 6: переменной y будет присвоена 6, и это присвоение само по себе вернёт ту же 6 и присвоит её переменной x. В Rust такое сделать не выйдет.

Expressions evaluate to a value and make up most of the rest of the code that you’ll write in Rust. Consider a math operation, such as 5 + 6, which is an expression that evaluates to the value 11. Expressions can be part of statements: In Listing 3-1, the 6 in the statement let y = 6; is an expression that evaluates to the value 6. Calling a function is an expression. Calling a macro is an expression. A new scope block created with curly brackets is an expression, for example:

Файл: src/main.rs

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

    println!("Значение y: {y}");
}

Это выражение …

{
    let x = 3;
    x + 1
}

is a block that, in this case, evaluates to 4. That value gets bound to y as part of the let statement. Note the x + 1 line without a semicolon at the end, which is unlike most of the lines you’ve seen so far. Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value. Keep this in mind as you explore function return values and expressions next.

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

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

Файл: src/main.rs

fn five() -> i32 {
    5
}

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

    println!("Значение x: {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`
Значение x: 5

The 5 in five is the function’s return value, which is why the return type is i32. Let’s examine this in more detail. There are two important bits: First, the line let x = five(); shows that we’re using the return value of a function to initialize a variable. Because the function five returns a 5, that line is the same as the following:

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

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

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

Файл: src/main.rs

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

    println!("Значение x: {x}");
}

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

Running this code will print The value of x is: 6. But what happens if we place a semicolon at the end of the line containing x + 1, changing it from an expression to a statement?

Файл: src/main.rs

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

    println!("Значение x: {x}");
}

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

Compiling this code will produce an error, as follows:

$ 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

The main error message, mismatched types, reveals the core issue with this code. The definition of the function plus_one says that it will return an i32, but statements don’t evaluate to a value, which is expressed by (), the unit type. Therefore, nothing is returned, which contradicts the function definition and results in an error. In this output, Rust provides a message to possibly help rectify this issue: It suggests removing the semicolon, which would fix the error.