Введение
Добро пожаловать в Functions. Этот лаба является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.
В этом лабе вы научитесь определять и вызывать функции в Rust с использованием ключевого слова fn и традиционного стиля именования в змеином регистре.
Функции
Функции широко распространены в коде на Rust. Вы уже видели одну из самых важных функций в этом языке: функцию main, которая является точкой входа для многих программ. Вы также видели ключевое слово fn, которое позволяет объявлять новые функции.
Создайте новый проект под названием functions:
cargo new functions
cd functions
Код на Rust использует стиль именования в змеином регистре для имен функций и переменных, при котором все буквы пишутся в нижнем регистре, а слова разделяются подчеркиваниями. Вот программа, содержащая пример определения функции:
Имя файла: 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 [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 [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 [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; — это инструкция.
Имя файла: 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 statement (`let`)
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: variable declaration using `let` is a statement
error[E0658]: `let` expressions in this position are unstable
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for
more information
Инструкция 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() {
1 let y = {2
let x = 3;
3 x + 1
};
println!("The value of y is: {y}");
}
Выражение [2] — это блок, который в этом случае вычисляется до 4. Это значение связывается с y как часть инструкции let [1]. Обратите внимание на строку без точки с запятой в конце [3], которая отличается от большинства строк, которые вы видели до сих пор. Выражения не включают завершающие точки с запятой. Если добавить точку с запятой в конец выражения, вы превратите его в инструкцию, и тогда оно не будет возвращать значение.牢记这一点,接下来我们将探讨函数返回值和表达式。
Функции с возвращаемыми значениями
Функции могут возвращать значения вызывающему коду. Мы не именуем возвращаемые значения, но мы должны объявить их тип после стрелки (->). В 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 [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, эта строка эквивалентна следующей:
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
Основное сообщение об ошибке, mismatched types (несовместимые типы), показывает основную проблему с этим кодом. Определение функции plus_one говорит, что она вернет i32, но инструкции не вычисляются до значения, что выражается как (), единичный тип. Поэтому ничего не возвращается, что противоречит определению функции и приводит к ошибке. В этом выводе Rust дает сообщение, которое может помочь исправить эту проблему: он предлагает удалить точку с запятой, что исправит ошибку.
Резюме
Поздравляем! Вы завершили лабораторную работу по функциям. Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.