Определение функций на Rust в LabEx

Beginner

This tutorial is from open-source community. Access the source code

Введение

Добро пожаловать в 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, чтобы улучшить свои навыки.