LoginSignup
2

More than 3 years have passed since last update.

RUSTの構造体の型定義とインスタンス化のおはなし

Posted at

まえがき

この記事はRUSTチュートリアルをZerobillbank社内メンバー(有志)で勉強がてら、可能な限りわかりやすく内容を書き出す企画の一部です。

なお、初学者のため(略)という免責を打ちつつ間違いなどあればコメントで教えていただけますと一同嬉しく思います!

概要

構造体の型定義とインスタンス化

構造体とは

関連する複数の変数たちをまとめて、一つの塊として表現できるものだよ。

構造体の定義方法

structキーワードを入れ、構造体の名前を定義するよ。

{}内に名前:型を定義する。

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

インスタンス化

構造体を呼び出して、値を入れることでインスタンス化するyo。

let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

値を引数から設定

fn build_user(email: String, username: String) -> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

フィールド初期化省略記法

上記の場合、keyとvalueの変数名が同一なので、省略して書くことが可能。

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

構造体が同じ別のインスタンス内のデータを流用して生成

.. によって、user2というインスタンス内で明示的に指定されていない要素についてはuser1と同じようにしてインスタンスを生成する記法。(まあ便利!)

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

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

プログラムお題

四角形の面積を返す関数

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
}

応答は以下の感じ

The area of the rectangle is 1500 square pixels.

これでも動くけど、もうちょっとリファクタリングしていくよ

タプル型を使う場合

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
}

要点

幅と高さを1つのタプルにまとめたよ

関数に渡す引数が1つでよくなったよ

問題点

タプルの0が幅で、1が高さであることを意識していないといけない。

コメントが残っていなかったら絶望的なコードになる。

そもそもデータに意味づけがされていないことも問題。

構造体を使う場合

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

要点

変数を構造体で表現したよ。

変数に意味づけがされて、何のためのデータであるかが明確になったよ

area関数の引数で、構造体Rectangleを&で参照してrectangleという名前にしているよ

デバッグ用(トレイトの継承?)

下記のインスタンスを出力しようとするこのコードを走らせると、こんな感じのエラーが出ます

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

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

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

:error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
(エラー: トレイト境界`Rectangle: std::fmt::Display`が満たされていません)

構造体をデバッグするためには、構造体定義の直前に#[derive(Debug)]という注釈が必要。

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

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

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

{:?}の出力

{:?}を使うことでprintln!にDebugの整形で出力できる。出力は下記になる。

rect1 is Rectangle { width: 30, height: 50 }

{:#?}の出力

{:?}の代わりに{:#?}を使うこともできる。その場合の出力は下記になる。

rect1 is Rectangle {
    width: 30,
    height: 50
}

他にもderive注釈で使えるトレイトが多く提供されているらしい(急に出てきやがったトレイトについては後の章で詳しくやります)

メソッド記法

enumかstructのなかで宣言される関数

メソッドの定義

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

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

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

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

structをimplで実装する感じ

&selfで自分自身(structの中身)を引数として受け取っている

関連関数

implブロック内にselfを引数に取らない関数を定義する

構造体の新規インスタンスを返すコンストラクタによく使用される..らしい

スタティックメソッドっぽい

複数の型をグループ化するもの…らしい

#![allow(unused_variables)]
fn main() {
    #[derive(Debug)]
    struct Rectangle {
        width: u32,
        height: u32,
    }

    impl Rectangle {
        fn square(size: u32) -> Rectangle {
            Rectangle { width: size, height: size }
        }
    }
}

この関連関数を呼び出すために、構造体名と一緒に::記法を使用します; 一例は

let sq = Rectangle::square(3);です。

この関数は、構造体によって名前空間分けされています。

::という記法は、関連関数とモジュールによって作り出される名前空間両方に使用されます。

複数のimplブロック

構造体は複数のimplで実装することもできる

#![allow(unused_variables)]
fn main() {
    #[derive(Debug)]
    struct Rectangle {
        width: u32,
        height: u32,
    }

    impl Rectangle {
        fn area(&self) -> u32 {
            self.width * self.height
        }
    }

    impl Rectangle {
        fn can_hold(&self, other: &Rectangle) -> bool {
            self.width > other.width && self.height > other.height
        }
    }
}

ではまた🙋‍♂️

さいごに

ZEROBILLBANKでは一緒に働く仲間を募集中です。
なんとかjsとか、ブロックチェーンとか、kubernetesとかでいろんなAPIを作るお仕事。
今のところエンジニアは5人くらい。スタートアップだけど、結構ホワイトで働きやすいです。

ZEROBILLBANK JAPAN Inc.

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2