注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
Rustにおける全てのプログラムには、少なくとも1つの関数、 main
関数があります。
fn main() { }
これは評価可能な関数定義の最も単純なものです。
前に言ったように、 fn
は「これは関数です」ということを示します。この関数には引数がないので、名前と丸括弧が続きます。そして、その本文を表す波括弧が続きます。
これが foo
という名前の関数です。
fn foo() { }
それでは、引数を取る場合はどうでしょうか。 これが数値を表示する関数です。
fn main() { fn print_number(x: i32) { println!("x is: {}", x); } }fn print_number(x: i32) { println!("x is: {}", x); }
これが print_number
を使う完全なプログラムです。
fn main() { print_number(5); } fn print_number(x: i32) { println!("x is: {}", x); }
見てのとおり、関数の引数は let
宣言と非常によく似た動きをします。
引数の名前にコロンに続けて型を追加します。
これが2つの数値を足して結果を表示する完全なプログラムです。
fn main() { print_sum(5, 6); } fn print_sum(x: i32, y: i32) { println!("sum is: {}", x + y); }fn main() { print_sum(5, 6); } fn print_sum(x: i32, y: i32) { println!("sum is: {}", x + y); }
関数を呼び出すときも、それを宣言したときと同様に、引数をコンマで区切ります。
let
と異なり、あなたは関数の引数の型を宣言 しなければなりません 。
これは動きません。
fn print_sum(x, y) { println!("sum is: {}", x + y); }
このエラーが発生します。
expected one of `!`, `:`, or `@`, found `)`
fn print_sum(x, y) {
これはよく考えられた設計上の決断です。 プログラムのすべての箇所で型推論をするという設計も可能ですが、一方で、そのように型推論を行なうHaskellのような言語でも、ドキュメント目的で型を明示するのはよい習慣だと言われています。 私たちの意見は、関数の型を明示することは強制しつつ、関数本体では型を推論するようにすることが、すべての箇所で型推論をするのとまったく型推論をしないことの間のすばらしいスイートスポットである、というところで一致しています。
戻り値についてはどうでしょうか。 これが整数に1を加える関数です。
fn main() { fn add_one(x: i32) -> i32 { x + 1 } }fn add_one(x: i32) -> i32 { x + 1 }
Rustの関数は値を1つだけ返します。そして、ダッシュ( -
)の後ろに大なりの記号( >
)を続けた「矢印」の後にその型を宣言します。
関数の最後の行が何を返すのかを決定します。
ここにセミコロンがないことに気が付くでしょう。
もしそれを追加すると、こうなります。
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について2つの興味深いことを明らかにします。それが式ベースの言語であること、そしてセミコロンが他の「波括弧とセミコロン」ベースの言語でのセミコロンとは違っているということです。 これら2つのことは関連します。
Rustは主として式ベースの言語です。 文には2種類しかなく、その他の全ては式です。
ではその違いは何でしょうか。
式は値を返しますが、文は返しません。
それが「not all control paths return a value」で終わった理由です。文 x + 1;
は値を返さないからです。
Rustには2種類の文があります。「宣言文」と「式文」です。
その他の全ては式です。
まずは宣言文について話しましょう。
いくつかの言語では、変数束縛を文としてだけではなく、式としても書けます。 Rubyではこうなります。
x = y = 5
しかし、Rustでは束縛を導入するための let
の使用は式では ありません 。
次の例はコンパイルエラーを起こします。
let x = (let y = 5); // 識別子を期待していましたが、キーワード `let` が見つかりました
ここでコンパイラが言っているのは、式の先頭が来ることを期待していましたが、 let
は式ではなく、文の先頭にしかなれませんよ、ということです。
次のことに注意しましょう。
既に束縛されている変数への代入(例えば y = 5
)は、その値が特に役に立つものではありませんが、やはり式です。
他の言語の中には、代入式を評価すると、代入された値(例えば、前の例では 5
)が返されるものもあります。
しかし、Rustの代入式はそれと異なり、評価すると空のタプル ()
が返されます。
なぜなら、代入された値には 単一の所有者 しかおらず、他のどんな値を返したとしても予想外の出来事になってしまうからです。
let mut y = 5; let x = (y = 6); // xは値 `()` を持っており、 `6` ではありません
Rustでの2種類目の文は 式文 です。 これの目的は式を文に変換することです。 実際にはRustの文法は文の後には他の文が続くことが期待されています。 これはそれぞれの式を区切るためにセミコロンを使うということを意味します。 これはRustが全ての行末にセミコロンを使うことを要求する他の言語のほとんどとよく似ていること、そして見られるRustのコードのほとんど全ての行末で、セミコロンが見られるということを意味します。
「ほとんど」と言ったところの例外は何でしょうか。 この例で既に見ています。
fn main() { fn add_one(x: i32) -> i32 { x + 1 } }fn add_one(x: i32) -> i32 { x + 1 }
この関数は i32
を返そうとしていますが、セミコロンを付ければ、それは代わりに ()
を返します。
Rustはこの挙動がおそらく求めているものではないということを理解するので、前に見たエラーの中で、セミコロンを削除することを提案するのです。
しかし、早期リターンについてはどうでしょうか。
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!("This function never returns!"); } }fn diverges() -> ! { panic!("This function never returns!"); }
panic!
は既に見てきた println!
と同様にマクロです。
println!
とは違って、 panic!
は実行中の現在のスレッドを与えられたメッセージとともにクラッシュさせます。
この関数はクラッシュを引き起こすので、決して値を返しません。そのため、この関数は「 !
」型を持つのです。「 !
」は「発散する(diverges)」と読みます。
もし diverges()
を呼び出すメイン関数を追加してそれを実行するならば、次のようなものが出力されるでしょう。
thread ‘<main>’ panicked at ‘This function never returns!’, hello.rs:2
もしもっと情報を得たいと思うのであれば、 RUST_BACKTRACE
環境変数をセットすることでバックトレースが得られます。
$ RUST_BACKTRACE=1 ./diverges
thread '<main>' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
3: 0x7f402773960e - rt::unwind::begin_unwind_inner::h2844b8c5e81e79558Bw
4: 0x7f4027738893 - rt::unwind::begin_unwind::h4375279447423903650
5: 0x7f4027738809 - diverges::h2266b4c4b850236beaa
6: 0x7f40277389e5 - main::h19bb1149c2f00ecfBaa
7: 0x7f402773f514 - rt::unwind::try::try_fn::h13186883479104382231
8: 0x7f402773d1d8 - __rust_try
9: 0x7f402773f201 - rt::lang_start::ha172a3ce74bb453aK5w
10: 0x7f4027738a19 - main
11: 0x7f402694ab44 - __libc_start_main
12: 0x7f40277386c8 - <unknown>
13: 0x0 - <unknown>
もしすでに RUST_BACKTRACE
変数がセットされており、それをアンセットできないなどの理由により値を上書きするのなら、 0
にセットすればバックトレースが取得されなくなります。
それ以外の全ての値では(環境変数はあるが値がない場合も含め)バックトレースがオンになります。
$ export RUST_BACKTRACE=1
...
$ RUST_BACKTRACE=0 ./diverges
thread '<main>' panicked at 'This function never returns!', hello.rs:2
note: Run with `RUST_BACKTRACE=1` for a backtrace.
RUST_BACKTRACE
はCargoの run
コマンドでも使えます。
$ RUST_BACKTRACE=1 cargo run
Running `target/debug/diverges`
thread '<main>' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
3: 0x7f402773960e - rt::unwind::begin_unwind_inner::h2844b8c5e81e79558Bw
4: 0x7f4027738893 - rt::unwind::begin_unwind::h4375279447423903650
5: 0x7f4027738809 - diverges::h2266b4c4b850236beaa
6: 0x7f40277389e5 - main::h19bb1149c2f00ecfBaa
7: 0x7f402773f514 - rt::unwind::try::try_fn::h13186883479104382231
8: 0x7f402773d1d8 - __rust_try
9: 0x7f402773f201 - rt::lang_start::ha172a3ce74bb453aK5w
10: 0x7f4027738a19 - main
11: 0x7f402694ab44 - __libc_start_main
12: 0x7f40277386c8 - <unknown>
13: 0x0 - <unknown>
発散する関数は任意の型としても使えます。
fn main() { fn diverges() -> ! { panic!("This function never returns!"); } let x: i32 = diverges(); let x: String = diverges(); }let x: i32 = diverges(); let x: String = diverges();
関数を指す(ポイントする)変数束縛も作れます。
fn main() { let f: fn(i32) -> i32; }let f: fn(i32) -> i32;
f
という変数束縛は、 i32
を引数として受け取り、 i32
を返す関数へのポインタになります。
例えばこうです。
fn plus_one(i: i32) -> i32 { i + 1 } // 型推論なし let f: fn(i32) -> i32 = plus_one; // 型推論あり let f = plus_one;
そして f
を使って関数を呼び出せます。
let six = f(5);