Rustで実装しながら言語仕様を学ぶ
以下では、cargoで生成したRustプロジェクトのソースコードを編集しながら、Rustでプログラムを記述するのに必要となるさまざまな言語仕様を説明していきます。
変数の取り扱い(p002-varサンプル)
Rustで変数を宣言するには「let <変数名>」とします。Rustの変数はデフォルトで変更不可です。リスト3では、値が一度代入されたname1変数に別の値を代入する処理(2)はエラーになります。なお(1)のprintln!文は、第1引数のテンプレート文字列に含まれる「{}」の部分に、第2引数以降の変数値を反映してコンソールに出力する処理です。
let name1 = "吉川英一"; println!("{} さん、おはようございます。", name1); // println!文 ...(1) // name1 = "木村武治"; // 変数値を変更できずエラーになる ...(2)
変数値を変更可能にするには、宣言時に「let mut <変数名>」とします。
let mut name2 = "吉川英一"; println!("{} さん、こんにちは。", name2); name2 = "木村武治"; // 変数値を変更できる println!("{} さん、こんにちは。", name2);
Rustでは、一連の処理の中で同じ変数名を再度宣言できます(「シャドーイング」と呼びます)。リスト5では、(1)で宣言した文字列型のname3変数を、(2)で整数型として再宣言しています。
let name3 = "一二三"; // 文字列(&str)型 ...(1) println!("{} さん、こんばんは。", name3); let name3 = 123; // 整数(i32)型で再宣言 ...(2) println!("{} さん、こんばんは。", name3);
変数は、初期値を代入するときに型が自動的に推定されますが、「let <変数名>:<型名>」と宣言して明示的に変数型を指定できます。
let name4: &str = "木村武治"; // 文字列(&str)型 let age: u32 = 53; // 正の整数(u32)型 println!("{}さん({}歳)", name4, age);
主な変数型を表1に示します。文字列は、文字列データの変更ができない&strと、変更ができるStringがあります。また、数値の型は、8ビットならi8/u8、16ビットならi16/u16...のように、ビット数によって複数の型があります。すべての変数型は公式サイトに列挙されています。
変数型 | 主なRustの変数型 |
---|---|
文字列 | &str, String |
符号あり整数 | i8, i16, i32, i64, i128 |
符号なし整数 | u8, u16, u32, u64, u128 |
浮動小数点数 | f32, f64 |
真偽値 | bool |
条件分岐(p003-ifサンプル)
多くのプログラミング言語同様、Rustでも条件分岐が利用できます。リスト7はif文の例です。この場合はprice = 10000なので(1)が実行されます。
let price: u32 = 10000; if price < 5000 { println!("お買い得!"); } else if price < 10000 { println!("お値段相当!"); } else { println!("高い!"); // これが実行される ...(1) }
C言語などのswitch-case文に対応する記述として、Rustではmatch文が利用できます。リスト8では、os変数が"iOS"、"Android"、それ以外の場合に対応する処理を記述できます。
let os = "Android"; match os { // os == "iOS"の場合 "iOS" => println!("iOSはAppleのスマホOSです。"), // os == "Android"の場合 "Android" => { // 中かっこで囲んで複数行を記述できる println!("Android:"); println!("GoogleのスマホOSです。"); }, // osがその他の値の場合 _ => println!("その他のOSです。"), }
繰り返し(p004-loopサンプル)
繰り返しもプログラム作成で頻繁に利用される処理です。リスト9はwhile文による繰り返しの例です。iが11、12、13の場合についてprintln!が実行されます。
let mut i = 11; while i <= 13 { // iが13以下の間、実行される println!("iPhone {}", i); i += 1; }
リスト10はfor文の例です。jが0から5まで、1ずつ増えながら繰り返し実行されます。
for j in 0 .. 6 { // 0を含み、6は含まない(0から5まで) println!("Street Fighter {}", j); }
Rustの繰り返しではloop文も利用できます。また、処理を中断して次のループに進むcontinueと、繰り返しを終了してループを抜けるbreakが利用できます(リスト11)。
let mut k = 0; loop { k += 1; // kが6または7のときは以下を処理せず次のループへ if k == 6 || k == 7 { continue; } println!("Galaxy Note {}", k); // kが10以上のときはループを抜ける if k >= 10 { break; } }
配列とベクタ(p005-arrayサンプル)
Rustでは、長さが固定の配列と、後から要素を追加・削除できるベクタが利用できます。配列はリスト12の通り、[<各要素>]記述で初期化します。「for <要素> in <配列>」で、配列の要素ごとに処理(ここではprintln!文)を実行できます。
let array = [1,2,3,4,5]; for e in array { println!("{}", e); }
ベクタはリスト13の通り、vec![<各要素>]で初期化後、要素を削除・追加できます。
let mut vec1 = vec![ "iPhone 12", "iPhone 12 Pro", (略) ]; vec1.pop(); // 最終要素を削除 vec1.drain(0..3); // 第0~2要素を削除 vec1.push("iPhone 13 Pro Max"); // 最終要素を追加
関数とモジュール(p006-fnサンプル)
Rustではmain関数以外に、任意の関数を追加できます。例えばリスト14のget_greet関数は、hour引数の値によって異なるあいさつ文を返します。関数には引数(ここではu32型のhour)と戻り値の型(ここではString)を指定します。
fn get_greet(hour: u32) -> String { if 6 <= hour && hour < 11 { // hourが6から10の間 return "おはようございます。".to_string(); } else if 11 <= hour && hour < 18 { // hourが11から17の間 return "こんにちは。".to_string(); } else { // それ以外 return "こんばんは。".to_string(); } }
関数はリスト15の通り呼び出せます。
let morning_greet = get_greet(9);
Rustでは別のソースコードをモジュールとして扱えます。ここではリスト14と類似のget_greet関数を、sub.rsファイルにリスト16の通り記述します。このとき(1)の通り、外部公開を表すpubキーワードを関数に付与しておきます。
pub fn get_greet(hour: u32) -> String { // pubキーワードで外部に公開 ...(1) (略) }
リスト16に記述したget_greet関数をmain.rsで利用するには、リスト17の通り記述します。(1)でsubモジュールをインポート後、(2)で「sub::get_greet」と記述します。
mod sub; // subモジュールをインポート ...(1) fn main() { (略) let morning_greet = sub::get_greet(9); // subモジュールの関数を利用 ...(2) (略) }
構造体(p007-structサンプル)
Rustでは構造体が利用できるほか、構造体にメソッドを追加して、クラスのような使い方もできます。リスト18(1)の通り、structに要素と型を指定して構造体を宣言します。構造体にメソッドを追加するには(2)のimplを利用します。追加するメソッドでは(3)の通り、第1引数に渡される自身の構造体(&self)を利用して処理を実装します。
// 構造体 ...(1) struct Phone { name: String, weight: f32, price: u32 } // 構造体にメソッドを追加 ...(2) impl Phone { // Phone構造体の内容を文字列で取得するメソッド ...(3) fn desc(&self) -> String { return format!("{}:重量 {}g, 価格 {}円", self.name, self.weight, self.price); } }
所有権と借用(p008-ownershipサンプル)
所有権は、オブジェクトを保持する責任を明確にする機能です。説明のため、整数型の要素x, yと内容を出力するprintメソッドを備える2次元ベクトルの構造体MyVecを考えます(リスト19)。
// 2次元ベクトルの構造体と、内容表示の関数 struct MyVec { x: i32, y: i32 } impl MyVec { fn print(&self) { println!("MyVec: ({}, {})", self.x, self.y); } }
このMyVec構造体を、ベクトルの正負を逆転するmyvec_invert関数の引数に与えて、リスト20の通り実行したとします(関数の詳細はサンプルコードを参照)。この場合、vec1の内容は(1)で所有権が関数内に移動してしまうため、その後vec1で参照することができなくなり、(2)でエラーになります。
let vec1 = MyVec { x: 1, y: 2 }; myvec_invert(vec1); // ここで所有権がvec1から関数内に移動 ...(1) // vec1.print(); // vec1には所有権がないのでprintメソッドを実行できない ...(2)
関数の引数に渡した後でその内容を操作する方法は大きくふたつあります。ひとつは、関数の戻り値で再び所有権を受け取る方法です。リスト21では、vec2を関数に渡した後、その戻り値で所有権を受け取る(1)ので、その後printメソッドが正常に実行できます(2)。
let mut vec2 = MyVec { x: 2, y: 4 }; vec2 = myvec_invert(vec2); // 戻り値で返却された所有権を受け取る ...(1) vec2.print(); // 正常に実行可能 ...(2)
もうひとつは、関数に変数の参照を渡す方法です。この方法を所有権の借用と呼びます。リスト22では、関数の引数に「&」をつけて、変数の参照を関数に渡しています。
let vec3 = MyVec { x: -1, y: 5 }; let vec4 = MyVec { x: 3, y: -2 }; // 関数の引数に参照を渡す let vec5 = myvec_add(&vec3, &vec4);
クレート(p009-crateサンプル)
クレートは、Rustプログラムの構成単位です。「crates.io」Webサイトで公開されているさまざまなクレートをプロジェクトに取り込んで利用できます。
ここでは乱数を生成できるrandクレートを例に使用法を説明します。まず、プロジェクト設定ファイルCargo.tomlファイルに、リスト23の通りクレートを記述します。
[dependencies] rand = "0.8.0" # randクレートを参照
main.rsファイルでは、リスト24の通りrandクレートを参照・利用します。(1)のuse文で、randクレートが提供するメソッド群を参照して(2)で利用しています。
use rand::prelude::*; // randクレートの便利なメソッド群 ...(1) fn main() { let x: u8 = random(); // u8の範囲内(0~255)のランダム値を取得 ...(2) (略) }
まとめ
本記事では、RustでWebAssemblyを実装するのに必要となるRust言語の基本的な記述方法を紹介しました。次回は、いよいよ本格的にWebAssemblyを実装していきます。