Функции
Функции широко распространены в коде на 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. Тип x
указан как i32. Когда мы передаём 5 в another_function, макрос
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 — язык, основанный на выражениях, это важное различие, которое нужно понять. В других языках таких различий может не быть, поэтому давайте рассмотрим, что такое инструкции и выражения и как их различия влияют на тела функций.
- Инструкции — это команды, которые выполняют какое-либо действие и не возвращают значение.
- Выражения вычисляются в результирующее значение.
Рассмотрим несколько примеров.
На самом деле мы уже использовали инструкции и выражения. Создание переменной
и присваивание ей значения с помощью ключевого слова let — это инструкция.
В листинге 3-1 let y = 6; является инструкцией.
fn main() {
let y = 6;
}
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)]` 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, и тогда и 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, но
инструкции не вычисляются в значение; это выражается типом (), unit-типом.
Следовательно, ничего не возвращается, что противоречит определению функции
и приводит к ошибке. В этом выводе Rust предоставляет сообщение, которое может
помочь исправить проблему: он предлагает удалить точку с запятой, что исправит
ошибку.