Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 5. Structs

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回は、Structsについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Structs(構造体)

構造体はタプルと似ていますが、各データに名前をつけることによって、値の意味を明確化できます。

Defining and Instantiating Structs - The Rust Programming Language

基本的な使用方法

下記のように struct により構造体 User を定義し、 let user = User によってUserの実体化をすることにより、 user.username として使用することができます。

struct User {
    username: String,
    sign_in_count: u64,
    active: bool,
}
fn main() {
    let user = User {
        username: String::from("User Name"),
        sign_in_count: 10,
        active: false,
    };

    println!("user.username -> {}", user.username);
    println!("user.sign_in_count -> {}", user.sign_in_count);
    println!("user.active -> {}", user.active);
}
user.username -> User Name
user.sign_in_count -> 10
user.active -> false

構造体であるため、フィールド名ではなく順序による実体化は許されません。

struct User {
    username: String,
    sign_in_count: u64,
    active: bool,
}
fn main() {
    let user = User {
        String::from("User Name"),
        10,
        false,
    };

    println!("user.username -> {}", user.username);
    println!("user.sign_in_count -> {}", user.sign_in_count);
    println!("user.active -> {}", user.active);
}
error: expected one of `,` or `}`, found `::`
 --> src/demo.rs:8:9
  |
7 |     let user = User {
  |                ---- while parsing this struct
8 |         String::from("User Name"),
  |               ^^ expected one of `,` or `}` here


error: expected identifier, found `10`
 --> src/demo.rs:9:3
  |
7 |     let user = User {
  |                ---- while parsing this struct
8 |         String::from("User Name"),
9 |         10,
  |         ^^ expected identifier


error: expected identifier, found keyword `false`
  --> src/demo.rs:10:3
   |
7  |     let user = User {
   |                ---- while parsing this struct
...
10 |         false,
   |         ^^^^^ expected identifier, found keyword
   |
help: you can escape reserved keywords to use them as identifiers
   |
10 |         r#false,
   |         ^^^^^^^


error[E0063]: missing fields `active`, `sign_in_count`, `username` in initializer of `User`
 --> src/demo.rs:7:13
  |
7 |     let user = User {
  |                ^^^^ missing `active`, `sign_in_count`, `username`
mutableな使用方法

user をmutableにすることにより、後続処理で user.username = ??? により、 username を変更することができます。

struct User {
    username: String,
    sign_in_count: u64,
    active: bool,
}
fn main() {
    let mut user = User {
        username: String::from("User Name"),
        sign_in_count: 10,
        active: false,
    };
    user.username = String::from("Mutable User Name");

    println!("user.username -> {}", user.username);
    println!("user.sign_in_count -> {}", user.sign_in_count);
    println!("user.active -> {}", user.active);
}
user.username -> Mutable User Name
user.sign_in_count -> 10
user.active -> false
関数を用いた実体化

下記のように関数を用いて実体化をすることも可能です。これにより、 active を固定化するなど、コーディングの高速化が図れそうですね。

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

fn main() {
    let user = build_user(String::from("Build User Name"));

    println!("user.username -> {}", user.username);
    println!("user.sign_in_count -> {}", user.sign_in_count);
    println!("user.active -> {}", user.active);
}

fn build_user(username: String) -> User {
    User {
        username: username,
        sign_in_count: 9,
        active: true,
    }
}
user.username -> Build User Name
user.sign_in_count -> 9
user.active -> true
短縮形を用いた実体化

パラメータ名とフィールド名が同一である場合は struct のフィールド名を短縮することが可能です。

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

fn main() {
    let user = build_user(String::from("Build User Name"));

    println!("user.username -> {}", user.username);
    println!("user.sign_in_count -> {}", user.sign_in_count);
    println!("user.active -> {}", user.active);
}

fn build_user(username: String) -> User {
    User {
        username,
        sign_in_count: 9,
        active: true,
    }
}
user.username -> Build User Name
user.sign_in_count -> 9
user.active -> true

短縮系は関数をかえさず、同一スコープ内でも使用することが可能です。

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

fn main() {
    let username = String::from("User Name");
    let sign_in_count = 8;
    let active = false;
    let user = User {
        username,
        sign_in_count,
        active,
    };

    println!("user.username -> {}", user.username);
    println!("user.sign_in_count -> {}", user.sign_in_count);
    println!("user.active -> {}", user.active);
}
user.username -> User Name
user.sign_in_count -> 8
user.active -> false
他のインスタンスを利用した実体化

下記の ..user1 のように、他のインスタンスを利用して、差分のみを記述することで実体化をすることが可能です。

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

fn main() {
    let user1 = User {
        username: String::from("User1 Name"),
        sign_in_count: 7,
        active: false,
    };
    let user2 = User {
        username: String::from("User2 Name"),
        ..user1
    };

    println!("user1.username -> {}", user1.username);
    println!("user1.sign_in_count -> {}", user1.sign_in_count);
    println!("user1.active -> {}", user1.active);
    println!("user2.username -> {}", user2.username);
    println!("user2.sign_in_count -> {}", user2.sign_in_count);
    println!("user2.active -> {}", user2.active);
}
user1.username -> User1 Name
user1.sign_in_count -> 7
user1.active -> false
user2.username -> User2 Name
user2.sign_in_count -> 7
user2.active -> false
タプル構造を利用した構造体

例えば、色の構造体を利用する場合、フィールド名を r,g,b,a として考えて記載すると、下記のようになります。

struct Color {
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

fn main() {
    let color = Color {
        r: 255,
        g: 254,
        b: 253,
        a: 252,
    };

    println!("({}, {}, {}, {})", color.r, color.g, color.b, color.a);
}
(255, 254, 253, 252)

フィールド名を利用せずタプル構造とした場合、下記のように記述ができます。

struct Color(u8, u8, u8, u8);

fn main() {
    let color = Color(255, 254, 253, 252);

    println!("({}, {}, {}, {})", color.0, color.1, color.2, color.3);
}
(255, 254, 253, 252)

出力方法

An Example Program Using Structs - The Rust Programming Language

下記のように、単純に出力しようとすると、std::fmt::Display ではないためコンパイルエラーとなります。

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

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

    println!("rect is {}", rect);
}
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
  --> src/demo.rs:12:25
   |
12 |     println!("rect is {}", rect);
   |                            ^^^^ `Rectangle` cannot be formatted with the default formatter
   |
   = 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
   = note: required by `std::fmt::Display::fmt

そのため、 #[derive(Debug)] を利用して、構造体に Debug 特性を導出して、デバッグフォーマットを利用できるようにすると共に、 {:?} による成形をすることで、出力させることが可能です。

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

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

    println!("rect is {:?}", rect);
}
rect is Rectangle { width: 30, height: 50 }

{:?} ではなく {:#?} を利用することで、もし構造体が長くなったとしても、見やすくなります。

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

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

    println!("rect is {:#?}", rect);
}
rect is Rectangle {
    width: 30,
    height: 50,
}

メソッド構文

Vuexの getters のように、構造体に対して関数をもたせることが可能です。

例えば、出力テストで利用した Rectangleを用いると、 impl (実装)により関数を定義して利用することが可能です。

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

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

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

    println!("rect is {:#?}", rect);
    println!("area -> {}", rect.area());
}
rect is Rectangle {
    width: 30,
    height: 50,
}
area -> 1500

また、同一 impl に複数の関数をもたせたり( height_squared )、 別 impl を利用したり( width_squared )、 関数内で関数を利用したり( demo )することも可能です。

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

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

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

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

    println!("rect is {:#?}", rect);
    println!("area -> {}", rect.area());
    println!("width_squared -> {}", rect.width_squared());
    println!("height_squared -> {}", rect.height_squared());
    println!("demo -> {}", rect.demo());
}
rect is Rectangle {
    width: 30,
    height: 50,
}
area -> 1500
width_squared -> 900
height_squared -> 2500
demo -> 3400

最後に

構造体については、ScalaでDDDの考えを取り入れていた時に、 Entity周りで大変お世話になった存在なので、 Rustにも存在していて重宝したいです。

次のenumについても、Value Object利用で大変重宝するので、楽しみです。