Каждая программа на Rust имеет по крайней мере одну функцию — main
:
fn main() { }
Это простейшее объявление функции. Как мы упоминали ранее, ключевое слово fn
объявляет функцию. За ним следует её имя, пустые круглые скобки (поскольку эта
функция не принимает аргументов), а затем тело функции, заключённое в фигурные
скобки. Вот функция foo
:
fn foo() { }
Итак, что насчёт аргументов, принимаемых функцией? Вот функция, печатающая число:
fn main() { fn print_number(x: i32) { println!("x равен: {}", x); } }fn print_number(x: i32) { println!("x равен: {}", x); }
Вот полная программа, использующая функцию print_number
:
fn main() { print_number(5); } fn print_number(x: i32) { println!("x равен: {}", x); }
Как видите, аргументы функций похожи на операторы let
: вы можете объявить тип
аргумента после двоеточия.
Вот полная программа, которая складывает два числа и печатает их:
fn main() { print_sum(5, 6); } fn print_sum(x: i32, y: i32) { println!("сумма чисел: {}", x + y); }fn main() { print_sum(5, 6); } fn print_sum(x: i32, y: i32) { println!("сумма чисел: {}", x + y); }
Аргументы разделяются запятой — и при вызове функции, и при её объявлении.
В отличие от let
, вы должны объявлять типы аргументов функции. Этот код не
скомпилируется:
fn print_sum(x, y) { println!("сумма чисел: {}", x + y); }
Вы увидите такую ошибку:
expected one of `!`, `:`, or `@`, found `)`
fn print_number(x, y) {
Это осознанное решение при проектировании языка. Бесспорно, вывод типов во всей программе возможен. Однако даже в Haskell считается хорошим стилем явно документировать типы функций, хотя в этом языке и возможен полный вывод типов. Мы считаем, что принудительное объявление типов функций при сохранении локального вывода типов — это хороший компромисс.
Как насчёт возвращаемого значения? Вот функция, которая прибавляет один к целому:
fn main() { fn add_one(x: i32) -> i32 { x + 1 } }fn add_one(x: i32) -> i32 { x + 1 }
Функции в Rust возвращают ровно одно значение, тип которого объявляется после
«стрелки». «Стрелка» представляет собой дефис (-
), за которым следует знак
«больше» (>
). Заметьте, что в функции выше нет точки с запятой. Если бы мы
добавили её:
fn add_one(x: i32) -> i32 { x + 1; }
мы бы получили ошибку:
error: not all control paths return a value
fn add_one(x: i32) -> i32 {
x + 1;
}
help: consider removing this semicolon:
x + 1;
^
Здесь показаны две интересные особенности Rust. Во-первых, это язык, ориентированный на выражения, и во-вторых, смысл точки с запятой отличается от смысла аналогичного символа в других языках с синтаксисом на основе фигурных скобок и точки с запятой. Эти две особенности связаны.
Rust — в первую очередь язык, ориентированный на выражения. Есть только два типа операторов, а всё остальное является выражением.
А в чём же разница? Выражение возвращает значение, в то время как оператор -
нет. Вот почему мы получаем здесь «not all control paths return a value»:
оператор х + 1;
не возвращает значение. Есть два типа операторов в Rust:
«операторы объявления» и «операторы выражения». Все остальное — выражения.
Давайте сначала поговорим об операторах объявления.
Оператор объявления — это связывание. В некоторых языках связывание переменных может быть записано как выражение, а не только как оператор. Например, в Ruby:
x = y = 5
Однако, в Rust использование let
для связывания не является выражением.
Следующий код вызовет ошибку компиляции:
let x = (let y = 5); // expected identifier, found keyword `let`
Здесь компилятор сообщил нам, что ожидал увидеть выражение, но let
является
оператором, а не выражением.
Обратите внимание, что присвоение уже связанной переменной (например: y = 5
)
является выражением, но его значение не особенно полезно. В отличие от других
языков, где результатом присваивания является присваиваемое значение (например,
5
из предыдущего примера), в Rust значением присваивания является пустой
кортеж ()
.
let mut y = 5; let x = (y = 6); // x будет присвоено значение `()`, а не `6`
Вторым типом операторов в Rust является оператор выражения. Его цель - превратить любое выражение в оператор. В практическом плане, грамматика Rust ожидает, что за операторами будут идти другие операторы. Это означает, что вы используете точку с запятой для отделения выражений друг от друга. Rust выглядит как многие другие языки, которые требуют использовать точку с запятой в конце каждой строки. Вы увидите её в конце почти каждой строки кода на Rust.
Из-за чего мы говорим «почти»? Вы это уже видели в этом примере:
fn main() { fn add_one(x: i32) -> i32 { x + 1 } }fn add_one(x: i32) -> i32 { x + 1 }
Наша функция объявлена как возвращающая i32
. Но если в конце есть точка с
запятой, то вместо этого функция вернёт ()
. Компилятор Rust обрабатывает эту
ситуацию и предлагает удалить точку с запятой.
А что насчёт досрочного возврата из функции? У нас есть для этого ключевое слово
return
:
fn foo(x: i32) -> i32 { return x; // дальнейший код не будет исполнен! x + 1 }
return
можно написать в последней строке тела функции, но это считается
плохим стилем:
fn foo(x: i32) -> i32 { return x + 1; }
Если вы никогда не работали с языком, в котором операторы являются выражениями,
предыдущее определение без return
может показаться вам странным. Но со
временем вы просто перестанете замечать это.
Для функций, которые не возвращают управление («расходящихся»), в Rust есть специальный синтаксис:
fn main() { fn diverges() -> ! { panic!("Эта функция не возвращает управление!"); } }fn diverges() -> ! { panic!("Эта функция не возвращает управление!"); }
panic!
— это макрос, как и println!()
, который мы встречали ранее. В отличие
от println!()
, panic!()
вызывает остановку текущего потока исполнения с
заданным сообщением.
Поскольку эта функция вызывает остановку исполнения, она никогда не вернёт
управление. Поэтому тип её возвращаемого значения обозначается знаком !
и
читается как «расходится». Значение расходящейся функции может быть использовано
как значение любого типа:
let x: i32 = diverges(); let x: String = diverges();