構造体を使ったプログラム例

構造体を使用したくなる可能性のあるケースを理解するために、長方形の面積を求めるプログラムを書きましょう。 単一の変数から始め、代わりに構造体を使うようにプログラムをリファクタリングします。

Cargoでrectanglesという新規バイナリプロジェクトを作成しましょう。このプロジェクトは、 長方形の幅と高さをピクセルで指定し、その面積を求めます。リスト5-8に、プロジェクトのsrc/main.rsで、 正にそうする一例を短いプログラムとして示しました。

ファイル名: src/main.rs

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        // 長方形の面積は、{}平方ピクセルです
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

リスト5-8: 個別の幅と高さ変数を指定して長方形の面積を求める

では、cargo runでこのプログラムを走らせてください:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/rectangles`
The area of the rectangle is 1500 square pixels.
(長方形の面積は、1500平方ピクセルです)

このコードは、各寸法を与えてarea関数を呼び出すことで長方形の面積を割り出すことができますが、 このコードはもっと簡潔で読みやすくすることができます。

このコードの問題点は、areaのシグニチャから明らかです:

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        // 長方形の面積は、{}平方ピクセルです
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

area関数は、1長方形の面積を求めるものと考えられますが、今書いた関数には引数が2つあり、 そしてこのプログラム内のどこを見ても、これらの引数に関連性があることが明確になっていません。 幅と高さを一緒にグループ化する方が、より読みやすく、扱いやすくなるでしょう。 それをする一つの方法については、第3章の「タプル型」節ですでに議論しました: タプルを使うのです。

タプルでリファクタリングする

リスト5-9は、タプルを使う別バージョンのプログラムを示しています。

ファイル名: src/main.rs

fn main() {
    let rect1 = (30, 50);

    println!(
        "The area of the rectangle is {} square pixels.",
        area(rect1)
    );
}

fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

リスト5-9: タプルで長方形の幅と高さを指定する

ある意味では、このプログラムはマシです。タプルのおかげで少し構造的になり、一引数を渡すだけになりました。 しかし別の意味では、このバージョンは明確性を失っています: タプルは要素に名前を付けないので、 タプルの要素に添え字でアクセスする必要があり、計算が不明瞭になったのです。

面積計算では幅と高さを混同しても問題ないですが、長方形を画面に描画したいとなると、これは問題になります! タプルの添え字0で、添え字1高さであることを肝に銘じておかなければなりません。 もし他人がこのコードを使用することになったら、彼らがこのことを見つけ出して肝に銘じておくのはより難しくなるでしょう。 データの意味をコードに載せていないことで、エラーを招きやすくなってしまいました。

構造体でリファクタリングする: より意味付けする

データのラベル付けで意味を付与するために構造体を使います。現在使用しているタプルを全体と一部に名前のある構造体に、 変形することができます。そう、リスト5-10に示したように。

ファイル名: src/main.rs

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

リスト5-10: Rectangle構造体を定義する

ここでは、構造体を定義し、Rectangleという名前にしています。波括弧の中でwidthheightというフィールドを定義し、 u32という型にしました。それからmain内でRectangleの特定のインスタンスを生成し、 幅を30、高さを50にしました。

これでarea関数は引数が一つになり、この引数は名前がrectangle、型はRectangle構造体インスタンスへの不変借用になりました。 第4章で触れたように、構造体の所有権を奪うよりも借用する必要があります。こうすることでmainは所有権を保って、 rect1を使用し続けることができ、そのために関数シグニチャと関数呼び出し時に&を使っているわけです。

area関数は、Rectangleインスタンスのwidthheightフィールドにアクセスしています。 (借用された構造体インスタンスのフィールドにアクセスしても、そのフィールドの値はムーブされないことに注意してください。 構造体の借用をよく使うのはこのためです) これで、areaの関数シグニチャは、我々の意図をズバリ示すようになりました: widthheightフィールドを使って、 Rectangleの面積を計算します。これにより、幅と高さが相互に関係していることが伝わり、 タプルの01という添え字を使うよりも、これらの値に説明的な名前を与えられるのです。プログラムの意図が明瞭になりました。

トレイトの導出で有用な機能を追加する

プログラムのデバッグをしている間に、Rectangleのインスタンスを出力し、フィールドの値を確認できると便利でしょう。 リスト5-11では、以前の章のように、println!マクロを試しに使用しようとしています。 ですが、これは動きません。

ファイル名: src/main.rs

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    // rect1は{}です
    println!("rect1 is {}", rect1);
}

リスト5-11: Rectangleのインスタンスを出力しようとする

このコードをコンパイルすると、こんな感じのエラーが出ます:

error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
(エラー: `Rectangle`は`std::fmt::Display`を実装していません)

println!マクロには、様々な整形があり、標準では、波括弧はDisplayとして知られる整形をするよう、 println!に指示するのです: 直接エンドユーザ向けの出力です。これまでに見てきた基本型は、 標準でDisplayを実装しています。というのも、1や他の基本型をユーザに見せる方法は一つしかないからです。 しかし構造体では、println!が出力を整形する方法は自明ではなくなります。出力方法がいくつもあるからです: カンマは必要なの?波かっこを出力する必要はある?全フィールドが見えるべき?この曖昧性のため、 Rustは必要なものを推測しようとせず、構造体はprintln!{}プレースホルダで使用されるDisplay実装を提供しないのです。

エラーを読み下すと、こんな有益な注意書きがあります:

   = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   (ヘルプ: `std::fmt::Display`は`Rectangle`に対して実装されていません)
   (注釈: フォーマット文字列では代わりに`{:?}`(またはpretty-printするためには{:#?})が使用できるかもしれません)

試してみましょう!pritnln!マクロ呼び出しは、println!("rect1 is {:?}", rect1);という見た目になるでしょう。 波括弧内に:?という指定子を書くと、println!Debugと呼ばれる出力整形を使いたいと指示するのです。 Debugトレイトは、開発者にとって有用な方法で構造体を出力させてくれるので、 コードをデバッグしている最中に、値を確認することができます。

変更してコードをコンパイルしてください。なに!まだエラーが出ます:

error[E0277]: `Rectangle` doesn't implement `Debug`
(エラー: `Rectangle`は`Debug`を実装していません)

しかし今回も、コンパイラは有益な注意書きを残してくれています:

   = help: the trait `Debug` is not implemented for `Rectangle`
   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
   (ヘルプ: トレイト`Debug`は`Rectangle`に対して実装されていません)
   (注釈: `Rectangle`に`#[derive(Debug)]`を追加するか、手動で`impl Debug for Rectangle`してください)

確かにRustにはデバッグ用の情報を出力する機能が備わっていますが、この機能を構造体で使えるようにするには、 明示的な選択をしなければならないのです。そうするには、構造体定義の直前に#[derive(Debug)]という外部属性を追加します。 そう、リスト5-12で示されている通りです。

ファイル名: src/main.rs

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {:?}", rect1);
}

リスト5-12: Debugトレイトを導出する属性を追加し、 Rectangleインスタンスをデバッグ用整形機で出力する

これでプログラムを実行すれば、エラーは出ず、以下のような出力が得られるでしょう:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle { width: 30, height: 50 }

素晴らしい!最善の出力ではないものの、このインスタンスの全フィールドの値を出力しているので、 デバッグ中には間違いなく役に立つでしょう。より大きな構造体があるなら、もう少し読みやすい出力の方が有用です; そのような場合には、println!文字列中の{:?}の代わりに{:#?}を使うことができます。 この例で{:#?}というスタイルを使用したら、出力は以下のようになるでしょう:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle {
    width: 30,
    height: 50,
}

Debug整形を使用して値を出力するためのもう一つの方法は、dbg!マクロを使用することです。 dbg!マクロは式の所有権を奪い(参照を取るprintln!とは対照的です)、その呼び出しが発生したコード内のファイル名と行番号とともに式を評価した結果を出力して、 その値の所有権を返します。

注釈: 標準出力コンソールストリーム(stdout)に出力するprintln!とは異なり、dbg!マクロの呼び出しは標準エラーコンソールストリーム(stderr)に出力します。 stderrstdoutについては12章の「標準出力ではなく標準エラーにエラーメッセージを書き込む」節でより詳しく触れます。

以下は、widthフィールドに代入される値と、rect1の構造体全体の値に関心がある場合の例です:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

dbg!は式が評価された値の所有権を返すため、式30 * scaleを囲うようにdbg!を書くことで、widthフィールドはここでdbg!の呼び出しをしなかった場合とまったく同じ値になります。 rect1の所有権は奪ってほしくないので、次のdbg!呼び出しではrect1への参照を使用しています。 この例の出力は以下のようになります:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/rectangles`
[src/main.rs:10:16] 30 * scale = 60
[src/main.rs:14:5] &rect1 = Rectangle {
    width: 60,
    height: 50,
}

出力の前半はsrc/main.rsの10行目からの出力です。 ここでは式30 * scaleをデバッグ出力していて、その結果の値は60です(整数に実装されているDebug整形を使用して出力されています)。 src/main.rsの14行目のdbg!呼び出しは&rect1の値を出力し、これはRectangle構造体です。 この出力は Rectangle型の pretty Debug整形を使用します。 dbg!マクロは、コードが何をしているのか理解しようとするときには非常に有用です!

Debugトレイトの他にも、Rustではderive属性で使えるトレイトが多く提供されており、独自の型に有用な振る舞いを追加することができます。 そのようなトレイトとその振る舞いは、付録Cで一覧になっています。 これらのトレイトを独自の動作とともに実装する方法だけでなく、独自のトレイトを生成する方法については、第10章で解説します。 また、deriveの他にも多数の属性が存在します; さらなる情報についてはRust Referenceの“Attributes”節を参照してください。

area関数は、非常に特殊です: 長方形の面積を算出するだけです。Rectangle構造体とこの動作をより緊密に結び付けられると、 役に立つでしょう。なぜなら、他のどんな型でもうまく動作しなくなるからです。 area関数をRectangle型に定義されたareaメソッドに変形することで、 このコードをリファクタリングし続けられる方法について見ていきましょう。