関数 function は、Rustのプログラムでよく見られるものです。すでに、Rustで最も重要な関数の一つである「main
」関数を目にしました。main
関数はプログラムの入口に当たるものです。また、fn
というキーワードにも顔馴染みです。これは「新しい関数を宣言する」ためのものでした。
Rustのプログラムでは、関数名や変数名に「スネークケース snake case」という伝統的な命名規則(定石)を用いています。この書法では、すべての文字を小文字書きし、単語の間をアンダースコア記号( _ )で繋ぐというものです。次のプログラムは、関数定義の例です。
《訳注》 スネークケース (snake case): 〔蛇書き、の意〕 単語の間をアンダースコアで繋げて表記する方法。なお、「大文字書き」は UPPERCASE、「小文字書き」は lowercase、単語間にスペースを入れずに各語の先頭だけを大文字にする書き方を「駱駝書き」 CamelCaseといいます。
《訳注》 第3.1章で説明のあった「定数の命名規則」は、
MAX_POINTS
のように「大文字書き」のスネークケース UPPER_SNAKE_CASEでした。
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Rustの関数定義は、fn
で書き始め、次に「関数名」、続いて丸括弧 ( )
です。波括弧({ })の部分は、コンパイラに関数の初めと終わりを示しています。
私たちは、後ろに丸括弧()
を付けた関数を記載することで、自分で定義した関数を呼び出せるようになります。上記の例では、another_function
という関数がプログラム内で定義されているので、main
関数の中から呼び出し可能です。ソースコードの中では、another_function
関数はmain
関数の後ろで定義されていますが、main
関数の前で定義することも可能な点に注意してください。Rustはプログラムのどの場所に関数が定義されているかということを気にしていません。プログラムのどこかで定義されていれば良いのです。
では、functionsというあたらしいバイナリ・プロジェクトを開始して、「関数」についてさらに詳しく調べてみましょう。上記のanother_function
プログラム例を「scr/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.
【訳】
functoinsコンパイル中・・・
開発モード(dev)コンパイル完了・・・
実行中・・・
Hello,woeld!(実行結果)
Another functions.(実行結果)
プログラムの各行は、main
関数に記述されている順序で実行されます。ですから、最初に「Hello, world!」メッセージが印字され、次にanother_function
関数が呼ばれて、そのメッセージが印字出力されます。
関数には「引数 parameters」を持たせることができます。このパラメータと呼ばれる「引数」は特別な変数で、関数のシグネチャー(関数の身分証明書のような目印情報)の一部になっています。関数に引数(パラメータ)があると、その引数には「具体的な実際の値」を与えることができます。技術的には、そのような具体的な実際の値は「実引数 arguments」と呼ばれ、パラメータのほうは「仮引数」と呼ばれていますが、日常会話レベルでは、人々はパラメータ(仮引数)もアーギュメント(実引数)も混ぜこぜに使い、「関数定義での変数」と「変数が呼び出されたときに受け渡される具体的な値」のどちらにも区別なく使われています。
《訳注》 引数のちがい (parameters, arguments): 上記の説明をまとめると:
英語 | 日本語訳 | 意味 |
---|---|---|
arguments | 実引数 | 関数を実行するときに、引数として指定される実際の具体的な値 |
parameters | 仮引数 | 関数で定義する特別な変数 |
|
《訳注》 関数のシグネチャー (a function's signature): 〔関数の署名、の意〕 関数やメソッドの名前、引数の数やデータ型、返り値の型などの組み合わせのことをシグネチャといいます。これにより、同じ名前の関数が異なる処理方法(メソッド)でも登録され、名前だけでは区別が出来ない場合でも、シグネチャが一致しなければ別メソッドとして扱うことが可能になります。シグネチャーは関数を適切に呼び分けるために必要な指紋のような情報なのです。
上記のanother_function
に「パラメータ(仮引数)」を書き足したものが、こちらです。
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
【訳】
プログラム「functoins」コンパイル中・・・
開発モード(dev)コンパイル完了・・・
実行中・・・
xの値は: 5 (実行結果)
関数another_function
の宣言にはx
というパラメータ(仮引数)が一つあり、x
のデータ型がi32
に指定されています。main関数からanother_function
を呼び出し、その時に実引数5
をanother_function
に渡すと、println!
マクロが5
をフォーマット文字列中(" "で囲まれた部分)の波括弧のある箇所に差し込み、出力します。
関数のシグネチャ―は、全パラメータ(仮引数)のデータ型を宣言しなければなりません。これはRustが言語設計で敢えて行なった仕様です。「関数の定義で型注釈が必要とされている」というその意味は、あなたがその関数をほかの場所で使う際に、コンパイラが「今回はどのデータ型を意図しているのか」を判断する必要がほとんどないということです。
複数のパラメータ(仮引数)を定義する場合は、パラメータの宣言毎にカンマで区切ります。
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
」の両方を含むテキストを出力します。
ではこのプログラムを実行してみましょう。functionsプロジェクトのsrc/main.rsファイルにあるプログラムをこの例題のプログラムで置き換えてください。では、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
※ 実行結果: 測定結果は: 5h
この関数の呼び出しに、引数value
の値として「5
」を、unit_label
の値に「'h'
」を渡しているので、プログラム結果に、この二つの値が表示されました。
《訳注》 main関数内で「
print_labeled_measuremnt
」関数を呼び出す際、2番目の引数(実引数/argument)がシングルクォーテーション「'」付きで指定されているのは、シングルクォーテーション「'」で囲むことでchar型のデータにするためであるらしい・・・。
関数の本体は、連続する「文」で出来ていますが、「式」で終わることもできます。これまで見てきた関数では、「式」で終わるものはありませんでしたが、「文」の一部としての「式」はありました。Rustは「式を中心としたプログラミング言語」ですので、「文」と「式」の違いを理解するのは重要なことです。他のプログラミング言語には、このような区別はありません。何が「文」で、何が「式」なのか、双方の違いが関数の本体にどのように影響するのか、をこれから見ていきましょう。
「文(Statements)」とは、なんらかの動作をし、値を返さない命令です。一方、「式(Expressions)」は、書かれた内容を判定して結果の値を返します。いくつかの事例で確認しましょう。
実際のところ、「文」も「式」もこれまでのプログラム例で使用しています。変数を生成し、変数にlet
キーワードを使って値を結び付けていたのは「文」です。次のプログラム3-1で、let y = 6;
と書いているが「文」です。
fn main() {
let y = 6;
}
関数の定義もまた「文」です: 上の例は、全体がそれ自体で「文」なのです。
「文」は値を返しません。このため、次のコード例で行なおうとしているような、let
文を他の変数に結び付けることはできません。エラー・メッセージを受け取ることになります。
fn main() {
let x = (let y = 6);
}
このプログラムを実行すると、このようなエラー・メッセージを受け取ることでしょう。
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
※ プログラム「functins」をコンパイル中
error: expected expression, found statement (`let`)
※ エラー: 「式」ではなく「文」が見つかりました
--> src/main.rs:2:14
※ scr/main.rs:行2:14文字目
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: variable declaration using `let` is a statement
※ 注記: `let`を用いて宣言する変数は「文」です
error[E0658]: `let` expressions in this position are experimental
※ エラー[E0658]: この位置での`let`「式」は暫定的(正式用法ではない)
--> 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
※ 注記:詳しくは#53667を参照
= help: you can write `matches!(<expr>, <pattern>)` instead of `let <pattern> = <expr>`
※ 修正案:`let <pattern> = <expr>`の代わりに`match!(<expre>, <pattern>)を使用する
warning: unnecessary parentheses around assigned value
※ 警告:値指定に不要な丸括弧あり
--> src/main.rs:2:13 (行2:13文字目)
|
2 | let x = (let y = 6);
| ^ ^
|
= note: `#[warn(unused_parens)]` on by default
※ 注記:デフォルトで`#[warn(unused_parens)]を適用
help: remove these parentheses
※ 修正案:丸括弧を除去
|
2 - let x = (let y = 6);
2 + let x = let y = 6;
|
For more information about this error, try `rustc --explain E0658`.
※ このエラーに関する情報は、`rustc --explain E0658`にて検索のこと
warning: `functions` (bin "functions") generated 1 warning
※ 警告:プログラム`functions`にで警告1件発生
error: could not compile `functions` due to 2 previous errors; 1 warning emitted
※ エラー:2件のエラーにより`functions`のコンパイル完了せず;警告1件発生
let y = 6
文は値を返さないので、変数x
に結び付けられるものがありません。この点が他のプログラミング言語、たとえば「C」や「Ruby」の動作と異なる点です。このような言語では代入の際「代入値」を返しますので、x = y = 6
のように書くことができ、x
にもy
にも6
が格納されます。けれどもRustではそうはなりません。
「式」はそこに書かれた内容を評価して値を出すもので、Rustで書かれているプログラムの残りの大半を占めています。5 + 6
のような数学演算を考えてみましょう。この式は演算内容を評価して、11
という値を導き出します。式は文の一部でもあり、先程の例3-1のように、let y = 6
文中の6
は、値6
を導き出す「式」です。関数の呼び出しも「式」です。マクロの呼び出しも「式」です。波括弧{ }
で括られた範囲も「式」です。たとえば、
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は値を返さなくなります。 次節の「戻り値のある関数」と式を探求する際にもこのことを覚えておいてください。
関数は、その関数を呼び出したプログラムに対して値を返すことができます。「戻り値」には名前を付けませんが、矢印「->
」を付けて、その後ろにデータ型を宣言しなければなりません。Rustでは、関数の戻り値は、関数本体のブロックにある最後の式の値と同じです。return
キーワードを使い、値を指定して、関数処理の途中でその関数から抜け出すことは出来ますが、ほとんどの関数は最後の式の判定結果を暗黙的に返します。次の例は、値を返す関数のサンプルです。
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 ※ (実行結果)xの値は: 5
関数 five
の 5
はこの関数の戻り値で、従って戻り値の型は i32
です。このことをもう少し詳しく見てみましょう。大切な部分が二つあります。
- 第一に、行
let x = five();
は変数を初期化するために、関数の戻り値を使うことを示しています。関数five
は5
を返しますので、この行は次の文と同じ事になります。
let x = 5;
- 第二に、関数
five
は引数(パラメータ)を持たずに戻り値の型を指定していますが、関数本体は数字の5
だけで。セミコロン(;)もありません。なぜならば、これが返したい値を示す「式」だからです。
別の例も見ましょう:
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
を含む行の終わりにセミコロンを付けてしまうと、その行は「式」から「文」へ変更され、エラー・メッセージを受け取ることになります。
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、24文字目
|
7 | fn plus_one(x: i32) -> i32 {
| -------- ^^^ expected `i32`, found `()` ※ `i32`の筈、`()`が見つかりません
| |
| implicitly returns `()` as its body has no tail or `return` expression
※ 関数本体に終端または`return`式がないため、暗黙的に(自動で)`()`「空の値」を返します
8 | x + 1;
| - help: consider removing this semicolon
※ 修正案: セミコロンの削除
For more information about this error, try `rustc --explain E0308`.
※ このエラーの詳細は`rustc --explain E0308`参照
error: could not compile `functions` due to previous error
※ エラー: 上記エラーのため、プログラム「functionsをコンパイルできません
エラー・メッセージの最初の部分に描かれている「型の不一致」が、このプログラムでの問題個所です。関数 plus_one
の定義では、この関数は i32
型(符号付き整数値)を返す、となっていますが、「文」では判定を行なわないので「値」を返しません〔つまり、戻り値がないので、ユニット型()
というデータの型でと表現されます。ユニット型とは何の値をもたない「空のタプル型」のことです〕。このため、なにも戻り値がなく、この関数の定義と矛盾しているため、結果としてエラーになります。エラー・メッセージでは、問題を修正するヒントもRustが教えてくれます。ここでは「セミコロンの削除」が提案されており、これでこの問題が解決します。