関数
関数は Rust のコードのいたるところで使われます。すでに、この言語で最も
重要な関数の1つである main 関数を見てきました。これは多くのプログラムの
エントリポイントです。また、新しい関数を宣言できる fn キーワードも
見てきました。
Rust のコードでは、関数名や変数名の慣習的なスタイルとして スネークケース を使用します。これは、すべての文字を小文字にし、単語をアンダースコアで区切る 形式です。次は、関数定義の例を含むプログラムです。
ファイル名: src/main.rs
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Rust では、fn の後に関数名と一組の丸括弧を書いて関数を定義します。
波括弧は、関数本体の開始位置と終了位置をコンパイラに伝えます。
定義した関数はどれでも、その名前の後に一組の丸括弧を書くことで呼び出せます。
another_function はプログラム内で定義されているので、main 関数の内側から
呼び出せます。ソースコードでは main 関数の 後 に another_function を
定義していますが、前に定義してもかまいません。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 では、パラメータを1つ追加しています。
ファイル名: 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 という名前のパラメータが1つあります。x
の型は i32 として指定されています。5 を another_function に渡すと、
println! マクロはフォーマット文字列内の x を含む一対の波括弧の位置に 5
を入れます。
関数シグネチャでは、各パラメータの型を 必ず 宣言しなければなりません。 これは 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 という名前の関数を2つのパラメータ付きで
作成しています。最初のパラメータの名前は value で、i32 です。2つ目は
unit_label という名前で、型は char です。その後、この関数は value と
unit_label の両方を含むテキストを表示します。
このコードを実行してみましょう。あなたの functions プロジェクトの
src/main.rs ファイルに現在入っているプログラムを先ほどの例で置き換え、
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
関数を、value の値として 5、unit_label の値として 'h' を渡して
呼び出したので、プログラムの出力にはそれらの値が含まれます。
文と式
関数本体は、一連の文で構成され、末尾に式がある場合もあります。ここまでに 扱ってきた関数には末尾の式は含まれていませんでしたが、文の一部としての式は すでに見ています。Rust は式ベースの言語なので、この違いを理解することは 重要です。他の言語では同じような区別がないこともあるので、文と式とは何か、 そしてその違いが関数本体にどのような影響を与えるのかを見ていきましょう。
- 文 は何らかの動作を実行する命令で、値を返しません。
- 式 は結果の値に評価されます。
いくつか例を見てみましょう。
実は、私たちはすでに文と式を使っています。変数を作成し、let キーワードを
使ってその変数に値を代入することは文です。リスト3-1では、let y = 6; は
文です。
fn main() {
let y = 6;
}
関数定義も文です。前の例全体が、それ自体で1つの文になっています。 (ただし、すぐに見るように、関数呼び出しは文ではありません。)
文は値を返しません。したがって、次のコードがそうしようとしているように、
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では、文 let y = 6; の中の 6 は、
値 6 に評価される式です。関数呼び出しは式です。マクロ呼び出しも式です。
波括弧で作られた新しいスコープブロックも式です。たとえば、
<span class="filename">ファイル名: src/main.rs</span>
```rust
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {y}");
}
この式:
{
let x = 3;
x + 1
}
はブロックであり、この場合は 4 に評価されます。この値は let 文の
一部として y に束縛されます。末尾にセミコロンがない 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
five の中の 5 は関数の戻り値であり、それが戻り値の型が i32 である理由です。
これをもう少し詳しく見てみましょう。重要な点は 2 つあります。まず、
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 を返すとされていますが、
文は値に評価されず、それはユニット型である () で表されます。したがって、
何も返されず、それが関数定義と矛盾してエラーになります。この出力では、Rust は
この問題を修正する助けになる可能性のあるメッセージも提供しています。セミコロン
を削除するよう提案しており、それによってエラーは修正されます。