Функции
Функции широко используются в коде на Rust. Вы уже видели одну из самых важных функций в языке: функцию main, которая является точкой входа для многих программ. Вы также видели ключевое слово fn, которое позволяет объявлять новые функции.
В коде Rust используется стиль именования snake case для функций и переменных, при котором все буквы строчные, а слова разделяются символами подчеркивания. Вот программа, содержащая пример определения функции:
Filename: 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 мы добавляем параметр:
Filename: 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: требование аннотаций типов в определениях функций означает, что компилятор почти никогда не потребует от вас использовать их в других местах кода, чтобы понять, какой тип вы имеете в виду. Компилятор также способен давать более полезные сообщения об ошибках, если он знает, какие типы ожидает функция.
При определении нескольких параметров разделяйте объявления параметров запятыми, вот так:
Filename: 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 другой переменной, как пытается сделать следующий код; вы получите ошибку:
Filename: 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. Вызов функции — это выражение. Вызов макроса — это выражение. Новый блок области видимости, созданный с помощью фигурных скобок, — это выражение, например:
Filename: 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 и указав значение, но большинство функций неявно возвращают последнее выражение. Вот пример функции, которая возвращает значение:
Filename: 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 без точки с запятой, потому что это выражение, значение которого мы хотим вернуть.
Давайте рассмотрим другой пример:
Filename: 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, превратив её из выражения в инструкцию, мы получим ошибку:
Filename: 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 предоставляет сообщение, которое может помочь исправить эту проблему: оно предлагает удалить точку с запятой, что исправит ошибку.