|
RustはC言語やC++に代わる新しいシステムプログラミング言語として開発されました。「安全性、速度、並行性」の3つの柱が重視されており、FirefoxやThunderbirdで知られるMozilla 社の公式プロジェクトで開発されているオープンソースの言語です。開発当初は破壊的な仕様変更を繰り返していましたが、2015年の1.0版リリース以降は、後方互換性を保ちながら精力的に新しいバージョンがリリースされています。現在(2021.12月)、バージョン1.57.0がリリースされています。
RustはC言語やC++でこれまで開発されてきたOSコアの開発において利用されることが多い言語です。Microsoftは2019年11月からWindowsの開発にRustを採用しており、GoogleもAndroid OSの開発にRustを採用すると発表しています。また、Linuxカーネルの開発にRustを採用する動きもあり、まさにC言語やC++に代わる新たな言語と言えるでしょう。
Rustは実行速度が速く、安全性が高いという特徴を持つ言語です。加えて、新しい言語であるため多くの言語を参考にした扱いやすい文法になっています。入門者にも最適な言語と言えるかもしれません。また、Rustは仮想マシンを使用せずに直接機械語にコンパイルできるため、C言語に匹敵する実行速度が実現できます。加えて、プログラム実行時に不要となったメモリ領域を自動で開放する「ガーベジコレクション」の機能がない代わりに、独自のメモリ管理機能を持っています。
C言語やC++を利用する際には、安全性の面でメモリ管理が特に重要視されます。セキュリティ上の脆弱性の多くはメモリ利用に関するバグに起因するとされており、C言語やC++におけるOS開発での弱点の一つとなっていました。Rustでは、独自のメモリ管理機能によってメモリ安全性を実現しています。
このページでは、MacOS上でRust言語を使用することを前提としているので、Windowsを使用するときは、公式サイトにおける説明を参照してください。ソースコードを作成すためのエディターは適宜好みのテキストエディターを使用してください。
簡単に段階的に学習するためのチュートリアルとして、Rust ツアーのサイトが非常に良くできています。自身のPCにRustをインストールしなくても、web 上でソースコードの実行ができる形式の学習になっています。また、 GitHubにrustlingsというクイズ形式のチュートリアルも参考になります。こちらは、自身のPCで練 習問題を解決しながら学習する形式になっています。
Last updated: 2021.12.7
Rust言語のインストールとコンパイルの方法 |
Rust 言語の日本語版の説明は公式サイトのBookにあります。
macOSかLinuxまたはその他のUnix系OSを使用している時、RustupをダウンロードしてRustをインストールするには、ターミナルで以下のコマンドを実行します。
- ~ $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
以下の表示が出ます。
- info: downloading installer
- Welcome to Rust!
- This will download and install the official compiler for the Rust
- programming language, and its package manager, Cargo.
- Rustup metadata and toolchains will be installed into the Rustup
- home directory, located at:
- /Users/koichi/.rustup
- This can be modified with the RUSTUP_HOME environment variable.
- The Cargo home directory located at:
- /Users/koichi/.cargo
- This can be modified with the CARGO_HOME environment variable.
- The cargo, rustc, rustup and other commands will be added to
- Cargo's bin directory, located at:
- /Users/koichi/.cargo/bin
- This path will then be added to your PATH environment variable by
- modifying the profile files located at:
- /Users/koichi/.profile
- /Users/koichi/.bash_profile
- /Users/koichi/.zshenv
- You can uninstall at any time with rustup self uninstall and
- these changes will be reverted.
- Current installation options:
- default host triple: x86_64-apple-darwin
- default toolchain: stable (default)
- profile: default
- modify PATH variable: yes
- 1) Proceed with installation (default)
- 2) Customize installation
- 3) Cancel installation
インストールを実行します。ここではデフォルトでインストールします。
- $ 1
と打ってインストールを実行します。
インストールがうまく行けば、以下の行が表示されます。
Rust is installed now. Great!
Rustの開発環境において、全てのツールは~/.cargo/binディレクトリにインストールされ、ここにrustc、cargo、rustupを含むRustのツールチェーンが置かれます。
よって、このディレクトリをPATH環境変数に含めるのが、Rustの開発者にとっての通例となっています。 インストールの際、rustupはPATHを設定しようとします。上記のインストール後、Pathは自動的に設定されます。通常は、パスの設定は
- $ export PATH="$HOME/.cargo/bin:$PATH"
と打ちますが、私の macOS では自動的に、/Users/koichi/.bash_profile に、
. "$HOME/.cargo/env"と記述されました。~/.cargo/env が追加されています。インストールが完了したことを確認するために以下のコマンドを打ってください。
- $ rustc --version
- rustc 1.57.0 (f1edd0429 2021-11-29)
バージョンが表示されるでしょう。
RustとCargoがインストールされたことを確かめるには、ターミナルで以下を実行してください
$ cargo --version
もしrustupで過去にRustをインストールしたならば、いつでもrustup updateを実行することでインストールしたものをアップデートできます。
Rustをアンインストールしたくなったときは、 rustup self uninstall を実行することでいつでもアンインストールできます。
次にソースコードのコンパイルを試験するために、例えば、main.rsというファイル名のソースファイルを作ります。Rustのファイルは常に .rsという拡張子で終わります。main.rsファイルを開き、以下のコードを入力してください。記号 // はそれ以降をコメント文であると指示します。
- fn main() {
- // 世界よ、こんにちは
- println!("Hello, world!");
- }
ファイル名に2単語以上使っているなら、アンダースコアで区切ります。例えば、helloworld.rsではなく、 hello_world.rsを使用します。
保存したmain.rs ファイルのあるディレクトリに移動して、コマンドラインから
- $ rustc main.rs
- $ ./main
と入力して、コンパイルと実行を行います。Hello, world!が確かに出力されたら、おめでとうございます!
rustcを実行するとき、リンカエラーが出たら、C言語のコンパイラーがインストールされていないからです。Rust パッケージの一部はC言語のコンパイラーを使用しますので、そうしたケースに備えて、C言語のコンパイラー、例えば、gccやclangなどをインストールしておく必要があります。
Cargoは、Rustのビルドシステム兼パッケージマネージャです。ほとんどのRustaceanはこのツールを使用して、 Rustプロジェクトの管理をしています。Cargoは、コードのビルドやコードが依存しているライブラリのダウンロード、 それらのライブラリのビルドなどの多くの仕事を扱ってくれるからです。今までに書いたような最も単純なRustプログラムは、依存がありません。従って、Hello, world!プロジェクトをCargoを使ってビルドしても、 Cargoのコードをビルドする部分しか使用しないでしょう。より複雑なRustプログラムを書くにつれて、 依存を追加し、Cargoでプロジェクトを開始したら、依存の追加は、遥かに簡単になるのです。
Cargoを使用して新しいプロジェクトを作成し、元のHello, world!プロジェクトとどう違うかを見ましょう。 上記Hello, world のディレクトリに移動します。以下を実行してください。
- $ cargo new hello_cargo --bin
- $ cd hello_cargo
最初のコマンドは、hello_cargoという新しいバイナリの実行可能ファイルを作成します。cargo newに渡した--bin引数が、 ライブラリとは対照的に実行可能なアプリケーションを作成します。プロジェクトをhello_cargoと名付け、 Cargoは、そのファイルを同名のディレクトリに作成します。
hello_cargoディレクトリに行き、ファイル構造を見てください。Cargoが2つのファイルと1つのディレクトリを生成してくれたことがわかるでしょう。 hello_cargo の直下に Cargo.tomlファイルと、srcディレクトリが作成されます。srcディレクトリにはソースコードのmain.rs ファイルがあります。
テキストエディタでCargo.tomlを開いてください。以下のように内容です。
- [package]
- name = "hello_cargo"
- version = "0.1.0"
- edition = "2021"
- # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
- [dependencies]
このファイルはTOML(Tom's Obvious, Minimal Language)フォーマットで、 Cargoの設定フォーマットです。
最初の行の[package]は、後の文がパッケージを設定していることを示すセクションヘッダーです。その後の3行が、Cargoがプログラムをコンパイルするのに必要な設定情報をセットします。 名前、バージョン、rustの版などです。
次に、src/main.rsを開いて覗いてみてください。Hello,worldのコードと同じものです。Cargoが生成したプロジェクトでは、srcディレクトリにソースコードが生成され、 最上位のディレクトリにCargo.toml設定ファイルが作成されます。CargoでHello, world!プログラムをビルドし、実行してみましょう。hello_cargoディレクトリから、 以下のコマンドを入力してプロジェクトをビルドしてください。
- $ cargo build
このコマンドは、hello_cargo ディレクトリ内で、target/debug/hello_cargoに実行可能ファイルを作成します。以下のコマンドで実行可能ファイルを実行できます。
- $ ./target/debug/hello_cargo
- ---- result ----
- Hello, world!
cargo buildでプロジェクトをビルドし、./target/debug/hello_cargoで実行することができました。代替的方法として、 cargo runを使用して、コードをコンパイルし、それから作成された実行可能ファイルを全部1コマンドで実行することもできます。
- $ cargo run
- ---- result ----
- Finished dev [unoptimized + debuginfo] target(s) in 0.00s
- Running `target/debug/hello_cargo`
- Hello, world!
Rust言語の初歩的説明 |
Hello, world!プログラムでいま何が起こったのか詳しく確認しましょう。 こちらがパズルの最初のピースです。
- fn main() {
- }
これらの行でRustで関数を定義しています。main関数は特別です。 常に全ての実行可能なRustプログラムで走る最初のコードになります。 1行目は、引数がなく、何も返さないmainという関数を宣言しています。引数があるなら、かっこ () の内部に記入します。
また、関数の本体が波括弧 {} に包まれていることにも注目してください。Rustでは、全ての関数本体の周りにこれらが必要になります。 スペースを1つあけて、開き波括弧を関数宣言と同じ行に配置するのがいいスタイルです。
main関数内には、以下のコードがあります。
- println!("Hello, world!");
この行が、この小さなプログラムの全作業をしています。テキストを画面に出力する作業です。 重要な記述方法が4つあります。まず、Rustのスタイルは、タブではなく、4スペースでインデントするということです。
2番目にprintln!はRustのマクロを呼び出すということです。とりあえず、!を使用すると、普通の関数ではなくマクロを呼んでいると理解します。
3番目に、"Hello, world!"文字列が見えます。この文字列を引数としてprintln!に渡すと、 この文字列が画面に表示されます。
4番目にこの行をセミコロン(;)で終えています。これは式が終わり、次の式の準備ができていることを示します。 Rustコードのほとんどの行は、セミコロンで終わります。
Rustプログラムを実行する前に、以下のように、rustcコマンドを入力し、ソースファイルの名前を渡すことで、 Rustコンパイラを使用してコンパイルする必要があります。
- $ rustc main.rs
これは C や C++の知識があるなら、gccやclangの使用法と似ていることがわかります。コンパイルに成功後、 Rustはバイナリの実行可能ファイル(main)を出力します。
Linux、macOS、WindowsのPowerShellなら、シェルで以下のようにlsコマンドを入力することで実行可能ファイルを見られます。
- $ ls
- main main.rs
公式サイトの例に沿って、古典的な初心者向けのプログラミング問題を実装してみましょう。 数当てゲームです。 これは以下のように動作します。
このプログラムは1から100までの乱数整数を生成します。 そしてプレーヤーに予想を入力するよう促します。予想を入力したら、プログラムは、 その予想が小さすぎたか大きすぎたかを出力します。予想が当たっていれば、ゲームは祝福メッセージを表示し、 終了します。
まず最初に、 Cargoを使って新規プロジェクトを作成します。
- $ cargo new guessing_game --bin
- $ cd guessing_game
最初のコマンドcargo newは、プロジェクト名を第1引数に取ります(guessing_gameですね)。 --binというフラグは、Cargoにバイナリ生成プロジェクトを作成させます。前節のものと似ていますね。 2番目のコマンドで新規プロジェクトのディレクトリに移動します。
上のコマンドを実行すると、`guessing_game` packageが作成され、srcディレクトリに main.rs が生成されています。src/main.rs を開いて、以下のコードに入れ替えてください。
- use std::io;
- fn main() {
- println!("Guess the number!"); // 数を当ててごらん
- println!("Please input your guess."); // ほら、予想を入力してね
- let mut guess = String::new();
- io::stdin().read_line(&mut guess)
- .expect("Failed to read line"); // 行の読み込みに失敗しました
- println!("You guessed: {}", guess); // 次のように予想しました: {}
- }
内容の説明は後にして、このコードを実行してみましょう。
- $cargo run
と打ちます。すると、以下のように表示されます。7と入力した時の結果は以下のようになります。
- Compiling guessing_game v0.1.0 (/Users/koichi/rust_project/guessing_game)
- Finished dev [unoptimized + debuginfo] target(s) in 4.93s
- Running `target/debug/guessing_game`
- Guess the number!
- Please input your guess.
- 7
- You guessed: 7
ここまでで、ゲームの最初の部分は完成になります。キーボードからの入力を受け付け、出力できています。
以下で、このコードの説明を行います。ユーザ入力を受け付け、結果を出力するためには、io(入/出力)ライブラリをスコープに導入する必要があります。 ioライブラリは、標準ライブラリ(stdとして知られています)に存在します。use文(use std::io;)で明示的にその型をスコープに導入しています。 std::ioライブラリを使用することで、ユーザ入力を受け付ける能力などの実用的な機能の多くを使用することができます。
fn構文(fn main() {)が関数を新しく宣言し、かっこの()は引数がないことを示し、波括弧の{が関数本体のスタート地点になります。println!は、文字列を画面に表示するマクロになります。
- println!("Guess the number!");
- println!("Please input your guess.");
このコードは、このゲームが何かを出力し、ユーザに入力を求めています。
let mut guess = String::new();
は新しく変数 guess を定義する let 文です。変数名(guess)の前にmutをつけて変数を可変にする方法が取られています。イコール記号(=)の反対側には、変数guessが束縛される値があります。この値は、 String::new関数の呼び出し結果であり、この関数は、String型のオブジェクトを返します。 String型は、標準ライブラリによって提供される文字列型で、 サイズ可変、UTF-8エンコードされたオブジェクトです。
プログラムの冒頭でuse std::ioとしているので、ioモジュールのstdin関数は以下のコードで呼び出しています。
io::stdin().read_line(&mut guess) .expect("Failed to read line");
.read_line(&mut guess)は、標準入力ハンドルのread_line メソッドを呼び出して、ユーザから入力を受け付けます。また、read_lineメソッドに対して、&mut guessという引数を一つ渡しています。read_lineメソッドの仕事は、ユーザが標準入力したものすべてを取り出し、文字列に格納することなので、 格納する文字列を引数として取ります。&という記号は、この引数が参照であることを表し、コードの複数箇所で同じデータにアクセスできるようになります。
以下のコマンドの説明をします。
println!("You guessed: {}", guess);
この行は、ユーザ入力を保存した文字列の中身を出力します。1組の波括弧の{}は、プレースホルダーの役目を果たします。 {}は値を所定の場所に保持する小さなカニのはさみと考えてください。波括弧を使って一つ以上の値を出力できます。 最初の波括弧の組は、フォーマット文字列の後に列挙された最初の値に対応し、 2組目は、2つ目の値、とそんな感じで続いていきます。1回のprintln!の呼び出しで複数の値を出力するコードは、 以下のような感じになります
- let x = 5;
- let y = 10;
- println!("x = {} and y = {}", x, y);
このコードは、x = 5 and y = 10と出力するでしょう。
次に、ユーザが数当てに挑戦する秘密の数字を生成する必要がありますが、これ以下のコードの紹介は省略します。興味ある方は、この公式サイトのチュートリアルを読んでください。
また、簡単に段階的に学習するためのチュートリアルとして、Rust ツアーのサイトが非常に良くできています。 GitHubにrustlingsという試行錯誤のクイズ形式のチュートリアルも参考になります。
変数とデータの型 |
Rust言語では、変数の値は標準で不変として取り扱われます。これは、 Rustが提供する安全性や簡便な並行性の利点を享受する形でコードを書くための選択の1つです。ディレクトリにcargo new --bin variablesコマンドを使って、 variablesという名前のプロジェクトを生成しましょう。src/main.rsファイルを以下のコードで書き換えてください。
- fn main() {
- let x = 5;
- println!("The value of x is: {}", x); // xの値は{}です
- x = 6;
- println!("The value of x is: {}", x);
- }
このコードを実行すると、次の出力に示されているようなエラーメッセージを受け取るはずです
- Compiling variables v0.1.0 (/Users/koichi/rust_project/variables)
- error[E0384]: cannot assign twice to immutable variable `x`
- --> src/main.rs:4:5
- |
- 2 | let x = 5;
- | -
- | |
- | first assignment to `x`
- | help: consider making this binding mutable: `mut x`
- 3 | println!("The value of x is: {}", x); // xの値は{}です
- 4 | x = 6;
- | ^^^^^ cannot assign twice to immutable variable
- For more information about this error, try `rustc --explain E0384`.
- error: could not compile `variables` due to previous error
変数は、標準でのみ、不変です。つまり、 前節の例のように変数名の前にmutキーワードを付けることで、可変にできるわけです。この値が変化できるようにするとともに、 mutにより、未来の読者に対してコードの別の部分がこの変数の値を変える可能性を示すことで、その意図を汲ませることができるのです。だから、まだ変数を可変にするという選択肢も残されています。以下のように、上記のコードを書き換えてください。
- fn main() {
- let mut x = 5;
- println!("The value of x is: {}", x); // xの値は{}です
- x = 6;
- println!("The value of x is: {}", x);
- }
このプログラムを走らせると、以下のような出力が得られます。
- $ cargo run
- Compiling variables v0.1.0 (/Users/koichi/rust_project/variables)
- Finished dev [unoptimized + debuginfo] target(s) in 3.83s
- Running `target/debug/variables`
- The value of x is: 5
- The value of x is: 6
こうして、可変性は時として非常に有益なこともあります。
変数の値を変更できないようにするといえば、他の多くの言語も持っている別のプログラミング概念、「定数」、を思い浮かべるでしょう。不変変数のように、定数は名前に束縛され、変更することが叶わない値のことですが、 定数と変数の間にはいくつかの違いがあります。まず、定数にはmutキーワードは使えません。 定数は標準で不変であるだけでなく、常に不変なのです。定数はletキーワードの代わりに、constキーワードで宣言し、値の型は必ず注釈しなければなりません。
- const number:i32 = 5;
のように定義します。ここで、i32 は32ビットの整数を表現します。整数表現のデフォルトは i32 です。小数点のついた数値は、32ビットあるいは64ビットの不動小数点表示があります。変数の値にもスカラー数値以外にも、bool 値及び配列表示があります。以下の例を見てください。
- fn main() {
- // Variables can be type annotated.
- let logical: bool = true;
- let a_float: f64 = 1.0; // Regular annotation
- let an_integer = 5i32; // Suffix annotation
- // Or a default will be used.
- let default_float = 3.0; // `f64`
- let default_integer = 7; // `i32`
- // A type can also be inferred from context
- let mut inferred_type = 12; // Type i64 is inferred from another line
- inferred_type = 4294967296i64;
- // A mutable variable's value can be changed.
- let mut mutable = 12; // Mutable `i32`
- mutable = 21;
- // Error! The type of a variable can't be changed.
- mutable = true;
- // Variables can be overwritten with shadowing.
- let mutable = true;
- }
上の例で、 let mutable = true という文が、シャドーイングを実行している表現です。前に定義した変数と同じ名前の変数を新しく宣言でき、 新しい変数は、前の変数を覆い隠すので、シャドーイングと言います。以下のようにして、同じ変数名を用いて変数を覆い隠し、letキーワードの使用を繰り返すことができます。
- fn main() {
- let x = 5;
- let x = x + 1;
- let x = x * 2;
- println!("The value of x is: {}", x);
- }
以下順に、 基本的なデータ型、関数、そしてフロー制御について簡略に説明します。
Rustにおける値は全て、何らかのデータ型になり、スカラー型と複合型の2種類からなります。スカラー型は、単独の値を表します。Rustには主に4つのスカラー型があります。整数、浮動小数点数、論理値、最後に文字です。整数とは、小数部分のない数値のことです。符号付(i)きと符号なし(u)の2種類の接頭語を使用して、整数値の型を宣言します。i64は64ビットの符号付きの整数値、u64は符号なしの64ビット整数値を表現します。デフォルトでの整数値はi32です。
浮動小数点数値に対しても、2種類の基本型、f32とf64があります。f32は32ビット表現、f64は64ビット表示になります。デフォルトでは、f64になります。以下の例を見てください。
- fn main() {
- let x = 2.0; // f64
- let y: f32 = 3.0; // f32
- }
Rustにも全数値型に期待されうる標準的な数学演算が用意されています。 足し算、引き算、掛け算、割り算、余りです。 以下の例では、let文での各演算の使用方法を見てください。
- fn main() {
- // 足し算
- let sum = 5 + 10;
- // 引き算
- let difference = 95.5 - 4.3;
- // 掛け算
- let product = 4 * 30;
- // 割り算
- let quotient = 56.7 / 32.2;
- // 余り
- let remainder = 43 % 5;
- }
これらの文の各式は、数学演算子を使用しており、一つの値に評価され、そして、変数に固定されます。
他の多くの言語同様、Rustの論理値型も取りうる値は二つしかありません。 trueとfalseです。 Rustの論理値型は、boolと指定されます。 以下はその例です。
- fn main() {
- let t = true;
- let f: bool = false; // 明示的型注釈付きで
- }
Rustには文字も用意されています。Rustのchar型は、 言語の最も基本的なアルファベット型であり、以下のコードでその使用方法の一例を見ることができます。
- fn main() {
- let c = 'z';
- let z = 'ℤ';
- let heart_eyed_cat = '😻'; //ハート目の猫
- }
複合型には2種類あります。C言語同様に、タプルと配列ですが、仕様は異なるところもあります。
タプルは、複数の型の何らかの値を一つの複合型にまとめ上げる一般的な手段です。タプルは、丸かっこの中にカンマ区切りの値リストを書くことで生成します。タプルの位置ごとに型があり、 タプル内の値はそれぞれ全てが同じ型である必要はありません。以下の例を見てください。
- fn main() {
- let tup: (i32, f64, u8) = (500, 6.4, 1);
- let (x, y, z) = tup;
- println!("The value of y is: {}", y);
- }
このプログラムは、まずタプルを生成し、それを変数tupに束縛しています。 それからletとパターンを使ってtup変数の中身を3つの個別の変数(x、y、zですね)に変換しています。 この過程は、分配と呼ばれます。
パターンマッチングを通しての分配の他にも、アクセスしたい値の番号をピリオド(.)に続けて書くことで、 タプルの要素に直接アクセスすることもできます。例です。
- fn main() {
- let x: (i32, f64, u8) = (500, 6.4, 1);
- let five_hundred = x.0;
- let six_point_four = x.1;
- let one = x.2;
- }
配列によっても、複数の値のコレクションを得ることができます。タプルと異なり、配列の全要素は、 同じ型でなければなりません。Rustの配列は、他の言語と異なっています。Rustの配列は、 固定長なのです。 一度宣言されたら、サイズを伸ばすことも縮めることもできません。Rustでは、配列に入れる要素は、角かっこ内にカンマ区切りリストとして記述します。
- fn main() {
- let a = [1, 2, 3, 4, 5];
- }
配列は、スタック上に確保される一塊のメモリです。添え字によって、 配列の要素に以下の例のようにアクセスすることができます。
- fn main() {
- let a = [1, 2, 3, 4, 5];
- let first = a[0];
- let second = a[1];
- }
配列のデータ型は [T;N] で定義できます。T は要素の型、N はコンパイル時に決まる固定長です。個々の要素は [x] 演算子によって取得できます。ここで x は取り出そうとする要素のインデックス(0 始まり)です。以下のように使用します。
- fn main() {
- let nums: [i32; 3] = [1, 2, 3];
- println!("{:?}", nums);
- println!("{}", nums[1]);
- }
関数とフロー制御 |
関数という概念はあらゆる言語で用いられるものですが、Rust言語では fn キーワードを用いて定義されます。Rustの関数と変数の命名規則は、スネークケース( some_variableのような命名規則)を使うのが慣例です。 スネークケースとは、全文字を小文字にし、単語区切りにアンダースコアを使うことです。 以下のプログラムで、サンプルの関数定義をご覧ください。
- fn main() {
- println!("Hello, world!");
- another_function();
- }
- fn another_function() {
- println!("Another function."); // 別の関数
- }
Rustにおいて関数定義は、fnキーワードで始まり、関数名の後に丸かっこの組が続きます。 波かっこが、コンパイラに関数本体の開始と終了の位置を伝えます。定義した関数は、名前に丸かっこの組を続けることで呼び出すことができます。 another_function関数がプログラム内で定義されているので、main関数内から呼び出すことができるわけです。 ソースコード中でanother_functionはmain関数の後に定義されていることに注目してください。 勿論、main関数の前に定義することもできます。コンパイラは、関数がどこで定義されているかは気にしません。 どこかで定義されていることのみ気にします。
関数は、引数を持つようにも定義できます。引数とは、関数シグニチャの一部になる特別な変数のことです。 関数に引数があると、引数の位置に実際の値を与えることができます。以下の例を見てください。
- fn main() {
- another_function(5);
- }
- fn another_function(x: i32) {
- println!("The value of x is: {}", x); // xの値は{}です
- }
関数は、それを呼び出したコードに値を返すことができます。戻り値に名前を付けはしませんが、 矢印(->)の後に型を書いて確かに宣言します。Rustでは、関数の戻り値は、関数本体ブロックの最後の式の値と同義です。
- fn add(x: i32, y: i32) -> i32 {
- return x + y;
- }
- fn main() {
- println!("{}", add(42, 13));
- }
Rustコードの実行フローを制御する最も一般的な文法要素は、if式とループです。if式によって、条件に依存して分岐させることができます。 もしif文の条件が合ったら、{}内の一連のコードを実行する、条件に合わなければ、if文の{}内のコードをジャンプする、あるいは、else {}内の一連のコードを実行する。以下の例を見てください。
- fn main() {
- let number = 3;
- if number < 5 {
- println!("condition was true"); // 条件は真でした
- } else {
- println!("condition was false"); // 条件は偽でした
- }
- }
if式は全て、キーワードのifから始め、条件式を続けます。今回の場合、 条件式は変数numberが5未満の値になっているかどうかをチェックします。 条件が真の時に実行したい一連のコードを条件式の直後に{}かっこで包んで配置します。
オプションとして、else式を含むこともでき、これによりプログラムは、 条件式が偽になった時に実行するコードを与えられます。仮に、else式を与えずに条件式が偽になったら、 プログラムは単にifブロックを飛ばして次のコードを実行しにいきます。
ifとelseを組み合わせてelse if式にすることで複数の条件を持たせることもできます。以下の例を見てください。
- fn main() {
- let number = 6;
- if number % 4 == 0 {
- // 数値は4で割り切れます
- println!("number is divisible by 4");
- } else if number % 3 == 0 {
- // 数値は3で割り切れます
- println!("number is divisible by 3");
- } else if number % 2 == 0 {
- // 数値は2で割り切れます
- println!("number is divisible by 2");
- } else {
- // 数値は4、3、2で割り切れません
- println!("number is not divisible by 4, 3, or 2");
- }
- }
このプログラムを実行すると、if式が順番に吟味され、最初に条件が真になった本体が実行されます。
一連のコードを1回以上実行できると、しばしば役に立ちます。この作業用に、 Rustにはいくつかのループが用意されています。ループは、本体内のコードを最後まで実行し、 直後にまた最初から処理を開始します。 Rustには3種類のループが存在します。 loopとwhileとforです。loop 文はC++言語にないものです。while 文と for 文は他の言語でもお馴染みの制御文なので分かりやすいと思います。
loop文の例を見ましょう。
- fn main() {
- let mut x = 0;
- loop {
- x += 1;
- if x == 42 {
- break;
- }
- }
- println!("{}", x);
- }
上の例のように、break によってループから脱出できます。while 文の使い方は以下の通りです。
- fn main() {
- let mut x = 0;
- while x != 42 {
- x += 1;
- }
- }
Rust の for ループは強力に改良されています。 イテレータとして評価される式を反復処理します。 イテレータとは何でしょうか? イテレータとは、項目がなくなるまで「次の項目は何ですか?」と質問することができるオブジェクトです。
- fn main() {
- for x in 0..5 {
- println!("{}", x);
- }
- for x in 0..=5 {
- println!("{}", x);
- }
- }
.. 演算子は、開始番号から終了番号の手前までの数値を生成するイテレータを作成します。..= 演算子は、開始番号から終了番号までの数値を生成するイテレータを作成します。このコードを実行すると以下の結果となります。
0 1 2 3 4 0 1 2 3 4 5
最後に、構造体について簡単に説明します。structまたは、構造体は、意味のあるグループを形成する複数の関連した値をまとめ、名前付けできる独自のデータ型です。 オブジェクト指向言語に対比していうと、structはオブジェクトのデータ属性みたいなものです。構造体はタプルと似ています。タプル同様、構造体の一部を異なる型にできます。 一方、タプルとは違って、各データ値に名前をつけるので、値の意味が明確になります。 この名前のおかげで、構造体はタプルに比して、より柔軟になるわけです。
以下の例を見てください。
- struct SeaCreature {
- // String は構造体である。
- animal_type: String,
- name: String,
- arms: i32,
- legs: i32,
- weapon: String,
- }
構造体の定義は、structキーワードを入れ、構造体全体に名前を付けます。構造体名は、 一つにグループ化されるデータの意義を表すものであるべきです。そして、波かっこ {} 内に、 データの名前と型を定義します。これはフィールドと呼ばれます。C++言語で struct を用いてクラスを定義する手法と類似していますが、少々異なります。
構造体を定義した後に使用するには、各フィールドに対して具体的な値を指定して構造体のインスタンスを生成します。 インスタンスは、構造体名を記述し、key: valueペアを含む波かっこを付け加えることで生成します。
全てのフィールドの値を指定してインスタンス化をする際は、インスタンス名= 構造体名 {...}とし、構造体のフィールドは演算子 . で取り出すことができます。関数の呼び出し String::from では構造体 String を作成し、この構造体と SeaCreature のフィールドを隣り合う形で スタック に入れられます。この説明はよく分からないかもしれませんが、以下の例を見てください。
- struct SeaCreature {
- animal_type: String,
- name: String,
- arms: i32,
- legs: i32,
- weapon: String,
- }
- fn main() {
- // SeaCreatureのデータはスタックに入ります。
- let ferris = SeaCreature {
- // String構造体もスタックに入りますが、
- // ヒープに入るデータの参照アドレスが一つ入ります。
- animal_type: String::from("crab"),
- name: String::from("Ferris"),
- arms: 2,
- legs: 4,
- weapon: String::from("claw"),
- };
- let sarah = SeaCreature {
- animal_type: String::from("octopus"),
- name: String::from("Sarah"),
- arms: 8,
- legs: 0,
- weapon: String::from("none"),
- };
- println!(
- "{} is a {}. They have {} arms, {} legs, and a {} weapon",
- ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon
- );
- println!(
- "{} is a {}. They have {} arms, and {} legs. They have no weapon..",
- sarah.name, sarah.animal_type, sarah.arms, sarah.legs
- );
- }
このコードの結果は以下の通りです。
Ferris is a crab. They have 2 arms, 4 legs, and a claw weapon Sarah is a octopus. They have 8 arms, and 0 legs. They have no weapon..
To be continued
未完成