CodeZine(コードジン)

特集ページ一覧

動かして学ぶ! Rustの言語仕様

Rust言語で作るWebバイナリファイル「WebAssembly」入門 第2回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
目次

Rustで実装しながら言語仕様を学ぶ

 以下では、cargoで生成したRustプロジェクトのソースコードを編集しながら、Rustでプログラムを記述するのに必要となるさまざまな言語仕様を説明していきます。

変数の取り扱い(p002-varサンプル)

 Rustで変数を宣言するには「let <変数名>」とします。Rustの変数はデフォルトで変更不可です。リスト3では、値が一度代入されたname1変数に別の値を代入する処理(2)はエラーになります。なお(1)のprintln!文は、第1引数のテンプレート文字列に含まれる「{}」の部分に、第2引数以降の変数値を反映してコンソールに出力する処理です。

[リスト3]変数はデフォルトで変更不可(p002-var/src/main.rs)
let name1 = "吉川英一";
println!("{} さん、おはようございます。", name1); // println!文 ...(1)
// name1 = "木村武治"; // 変数値を変更できずエラーになる ...(2)

 変数値を変更可能にするには、宣言時に「let mut <変数名>」とします。

[リスト4]変更可能な変数を利用する例(p002-var/src/main.rs)
let mut name2 = "吉川英一";
println!("{} さん、こんにちは。", name2);
name2 = "木村武治"; // 変数値を変更できる
println!("{} さん、こんにちは。", name2);

 Rustでは、一連の処理の中で同じ変数名を再度宣言できます(「シャドーイング」と呼びます)。リスト5では、(1)で宣言した文字列型のname3変数を、(2)で整数型として再宣言しています。

[リスト5]シャドーイングの例(p002-var/src/main.rs)
let name3 = "一二三"; // 文字列(&str)型 ...(1)
println!("{} さん、こんばんは。", name3);
let name3 = 123; // 整数(i32)型で再宣言 ...(2)
println!("{} さん、こんばんは。", name3);

 変数は、初期値を代入するときに型が自動的に推定されますが、「let <変数名>:<型名>」と宣言して明示的に変数型を指定できます。

[リスト6]変数型の指定(p002-var/src/main.rs)
let name4: &str = "木村武治"; // 文字列(&str)型
let age: u32 = 53;  // 正の整数(u32)型
println!("{}さん({}歳)", name4, age);

 主な変数型を表1に示します。文字列は、文字列データの変更ができない&strと、変更ができるStringがあります。また、数値の型は、8ビットならi8/u8、16ビットならi16/u16...のように、ビット数によって複数の型があります。すべての変数型は公式サイトに列挙されています。

表1 Rustの主な変数型
変数型 主なRustの変数型
文字列 &str, String
符号あり整数 i8, i16, i32, i64, i128
符号なし整数 u8, u16, u32, u64, u128
浮動小数点数 f32, f64
真偽値 bool

条件分岐(p003-ifサンプル)

 多くのプログラミング言語同様、Rustでも条件分岐が利用できます。リスト7はif文の例です。この場合はprice = 10000なので(1)が実行されます。

[リスト7]ifの利用例(p003-if/src/main.rs)
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"、それ以外の場合に対応する処理を記述できます。

[リスト8]matchの利用例(p003-if/src/main.rs)
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!が実行されます。

[リスト9]whileの利用例(p004-loop/src/main.rs)
let mut i = 11;
while i <= 13 { // iが13以下の間、実行される
    println!("iPhone {}", i);
    i += 1;
}

 リスト10はfor文の例です。jが0から5まで、1ずつ増えながら繰り返し実行されます。

[リスト10]forの利用例(p004-loop/src/main.rs)
for j in 0 .. 6 { // 0を含み、6は含まない(0から5まで)
    println!("Street Fighter {}", j);
}

 Rustの繰り返しではloop文も利用できます。また、処理を中断して次のループに進むcontinueと、繰り返しを終了してループを抜けるbreakが利用できます(リスト11)。

[リスト11]loopの利用例(p004-loop/src/main.rs)
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!文)を実行できます。

[リスト12]配列の利用例(p005-array/src/main.rs)
let array = [1,2,3,4,5];
for e in array {
    println!("{}", e);
}

 ベクタはリスト13の通り、vec![<各要素>]で初期化後、要素を削除・追加できます。

[リスト13]ベクタの利用例(p005-array/src/main.rs)
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)を指定します。

[リスト14]get_greet関数(p006-fn/src/main.rs)
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の通り呼び出せます。

[リスト15]get_greet関数の呼び出し(p006-fn/src/main.rs)
let morning_greet = get_greet(9);

 Rustでは別のソースコードをモジュールとして扱えます。ここではリスト14と類似のget_greet関数を、sub.rsファイルにリスト16の通り記述します。このとき(1)の通り、外部公開を表すpubキーワードを関数に付与しておきます。

[リスト16]subモジュールに記述したget_greet関数(p006-fn/src/sub.rs)
pub fn get_greet(hour: u32) -> String { // pubキーワードで外部に公開 ...(1)
(略)
}

 リスト16に記述したget_greet関数をmain.rsで利用するには、リスト17の通り記述します。(1)でsubモジュールをインポート後、(2)で「sub::get_greet」と記述します。

[リスト17]subモジュールを利用する記述(p006-fn/src/main.rs)
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)を利用して処理を実装します。

[リスト18]構造体の記述(p007-struct/src/main.rs)
// 構造体 ...(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)。

[リスト19]2次元ベクトルの構造体(p008-ownership/src/main.rs)
// 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)でエラーになります。

[リスト20]所有権が関数内に移動する例(p008-ownership/src/main.rs)
let vec1 = MyVec { x: 1, y: 2 };
myvec_invert(vec1); // ここで所有権がvec1から関数内に移動 ...(1)
// vec1.print(); // vec1には所有権がないのでprintメソッドを実行できない ...(2)

 関数の引数に渡した後でその内容を操作する方法は大きくふたつあります。ひとつは、関数の戻り値で再び所有権を受け取る方法です。リスト21では、vec2を関数に渡した後、その戻り値で所有権を受け取る(1)ので、その後printメソッドが正常に実行できます(2)。

[リスト21]関数の戻り値で所有権を受け取る例(p008-ownership/src/main.rs)
let mut vec2 = MyVec { x: 2, y: 4 };
vec2 = myvec_invert(vec2); // 戻り値で返却された所有権を受け取る ...(1)
vec2.print(); // 正常に実行可能 ...(2)

 もうひとつは、関数に変数の参照を渡す方法です。この方法を所有権の借用と呼びます。リスト22では、関数の引数に「&」をつけて、変数の参照を関数に渡しています。

[リスト22]所有権の借用を利用する例(p008-ownership/src/main.rs)
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サイトで公開されているさまざまなクレートをプロジェクトに取り込んで利用できます。

図3 様々なクレートを公開している「crates.io」

図3 さまざまなクレートを公開している「crates.io」

 ここでは乱数を生成できるrandクレートを例に使用法を説明します。まず、プロジェクト設定ファイルCargo.tomlファイルに、リスト23の通りクレートを記述します。

[リスト23]randクレートの指定(p009-crate/Cargo.toml)
[dependencies]
rand = "0.8.0" # randクレートを参照

 main.rsファイルでは、リスト24の通りrandクレートを参照・利用します。(1)のuse文で、randクレートが提供するメソッド群を参照して(2)で利用しています。

[リスト24]randクレートの利用(p009-crate/src/main.rs)
use rand::prelude::*; // randクレートの便利なメソッド群 ...(1)
fn main() {
    let x: u8 = random(); // u8の範囲内(0~255)のランダム値を取得 ...(2)
(略)
}

まとめ

 本記事では、RustでWebAssemblyを実装するのに必要となるRust言語の基本的な記述方法を紹介しました。次回は、いよいよ本格的にWebAssemblyを実装していきます。

参考資料



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:Rust言語で作るWebバイナリファイル「WebAssembly」入門

著者プロフィール

  • WINGSプロジェクト  吉川 英一(ヨシカワ エイイチ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5