14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rust勉強中 - その15 -> 構造体

Last updated at Posted at 2019-10-14

自己紹介

出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。

Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。

環境

新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.38.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

前回

前回はModuleとテスト、ドキュメントについて学びました。
Rust勉強中 - その14

構造体

構造体はいろんな型の複数の値を一つにまとめた値です。
Rustには、名前付きフィールド型、タプル型、ユニット型の構造体があります。

名前付きフィールド型

これは、他の言語でもよく見る構造体だと思います。定義は以下のようになります。

struct StructName {
    field1: type1,
    field2: type2
}

例えば以下のような感じです。

struct Book {
    isbn:     String,
    size:     (usize, usize),
    category: String
}

この構造体を初期化するには以下のようにします。

fn main() {
    let book1 = Book {
        isbn: "000-0-000000-00-0".to_string(),
        size: (20, 40),
        category: "Rust".to_string()
    };
    let isbn = "000-0-000000-00-1".to_string();
    let size = (30, 50);
    let category = "Python".to_string();
    let book2 = Book {isbn, size, category};
    // let book3 = Book {size, category}; // Error: missing isbn
    println!("book1 = isbn: {}, size: {:?}, category: {}", book1.isbn, book1.size, book1.category);
    println!("book2 = isbn: {}, size: {:?}, category: {}", book2.isbn, book2.size, book2.category);
}
book1 = isbn: 000-0-000000-00-0, size: (20, 40), category: Rust
book2 = isbn: 000-0-000000-00-1, size: (30, 50), category: Python

変数book1では直接フィールドと値を書き込んで構造体を初期化しています。
変数book2ではフィールドと同じ名前を持つ変数にあらかじめ値を格納し、構造体を初期化しています。

構造体やフィールドにpubを付けるとパブリックになります。それ以外はプライベートとなり外部からアクセスできません。

mod module_book {
    // public struct and field
    pub struct PublicBook {
        pub isbn:     String,
        pub size:     (usize, usize),
        pub category: String
    }
    // public struct and private field
    pub struct PublicPrivateBook {
        isbn:     String,
        size:     (usize, usize),
        category: String
    }
}

fn main() {
    ...
    let book3 = module_book::PublicBook {
        isbn: "000-0-000000-00-2".to_string(),
        size: (20, 40),
        category: "C".to_string()
    };
    println!("book3 = isbn: {}, size: {:?}, category: {}", book3.isbn, book3.size, book3.category);
    // let book4 = module_book::PublicPrivateBook { // Error: field is private
    //     isbn: "000-0-000000-00-3".to_string(),
    //     size: (20, 40),
    //     category: "C++".to_string()
    // };
}

また、..を使うことでほとんどのフィールドが同じ構造体のコピーを作ることができます。

fn main() {
    ...
    let book5 = Book{size: (10, 10), ..book1}; // almost the same structure
    println!("book5 = isbn: {}, size: {:?}, category: {}", book5.isbn, book5.size, book5.category);
}
book5 = isbn: 000-0-000000-00-0, size: (10, 10), category: Rust

変数book5は、変数book1が持つ構造体のsizeフィールド以外は同じ構造体を持ちます。

タプル型

タプルに型名が付いたような構造体を定義できます。

struct BookSize((usize, usize), (usize, usize), (usize, usize));

fn main() {
    ...
    let book_size = BookSize(book1.size, book2.size, book5.size);
    println!("book_size = {:?}, {:?}, {:?}", book_size.0, book_size.1, book_size.2);
}

本当にタプルのようで、アクセスも定数で行います。
名前付きフィールド型とタプル型は似ているので、どちらが綺麗に書けるかによって使い分けると良いそうです。

ユニット型

要素を持たない構造体です。

struct UnitBook;

今のところ私には使い方が分かりませんが、トレイトを扱う際に便利なんだそうです。それまでお預けです。

メソッドの関連付け

構造体にimplブロックを使ってメソッドを関連付けることができます。
先ほどのPublicPrivateBook構造体にimplブロックを使ってメソッドを定義します。

...
mod module_book {
    ...
    // public struct and private field
    pub struct PublicPrivateBook {
        isbn:     String,
        size:     (usize, usize),
        category: String
    }
    impl PublicPrivateBook {
        pub fn new() -> PublicPrivateBook {
            PublicPrivateBook {
                isbn: "000-0-000000-00-0".to_string(),
                size: (0, 0),
                category: "".to_string()
            }
        }
       pub fn show(&self) {
            println!("book = isbn: {}, size: {:?}, category: {}", self.isbn, self.size, self.category);
        }
       pub fn isbn(&mut self, isbn: &str) {
           self.isbn = isbn.to_string();
       }
    }
}

fn main() {
    ...
    let mut book4 = module_book::PublicPrivateBook::new();
    book4.isbn("000-0-000000-00-3");
    // println!("book4 = isbn: {}, size: {:?}, category: {}", book4.isbn, book4.size, book4.category); // Error: field is private
    book4.show();
}
book = isbn: 000-0-000000-00-3, size: (0, 0), category:

メソッドの引数selfは、呼び出した値自身を指します。
メソッドを定義することで、そのメソッドからの呼び出しのみプライベートな値を変更出来たりします。
selfを渡さないメソッドは、スタティックメソッドと呼ばれ、その構造体自身に関連付けされます。スタティックメソッドはよくコンストラクタとして定義されたりし、上記ではnewを使って初期化された構造体自身を返すメソッドを定義しています。また、newという名前は初期化された値を返すという意味を持つメソッド名として付ける慣習があるそうです。

また、Selfとすれば後述するジェネリック構造体に限らず、その構造体自身という意味で使うことができます。

mod module_book {
    ...
    // public struct and private field
    pub struct PublicPrivateBook {
        isbn:     String,
        size:     (usize, usize),
        category: String
    }
    impl PublicPrivateBook {
        ...
        pub fn new2() -> Self {
            PublicPrivateBook {
                isbn: "000-0-000000-00-0".to_string(),
                size: (0, 0),
                category: "".to_string()
            }
        }
        ...
     }
}

ジェネリック構造体

型パラメータを用いて任意の型に対する構造体を定義することができます。

struct GenericBook<T> {
    book: module_book::PublicPrivateBook,
    seal: Vec<T>
}
impl<T> GenericBook<T> {
    pub fn new() -> Self {
        GenericBook {book: module_book::PublicPrivateBook::new(), seal: Vec::new()}
    }
    pub fn seal(&mut self, t:T) {
        self.seal.push(t);
    }
    pub fn show(&self) {
        self.book.show();
        // println!("seal = {:?}", self.seal); // Error: `T` doesn't implement `std::fmt::Debug`
    }
}

fn main() {
    let mut generic_book = GenericBook::<String>::new();
    generic_book.seal("abc".to_string());
    generic_book.show();
}

上記では、任意のT型を持った構造体の要素Vecを定義しています。これにより、あらゆる型に対応した構造体を扱えることになります。
ジェネリック構造体のimplブロックはimpl<T> StructName<T> {...}のようになります。メソッドの定義部分では型Tの表記は推論により省略できます。

標準トレイトの実装

この節までに書いた構造体ではクローンやデバッグ出力ができません。

struct Book {
    isbn:     String,
    size:     (usize, usize),
    category: String
}
...
fn main {
    ...
    let isbn = "000-0-000000-00-1".to_string();
    let size = (30, 50);
    let category = "Python".to_string();
    let book2 = Book {isbn, size, category};
    println!("book2 = {:?}", book2.clone());
}

そこで、#[derive]属性を用いることで、あらかじめ用意されている標準トレイトを簡単に実装できます。

// private struct and field
#[derive(Clone, Debug)]
struct Book {
    isbn:     String,
    size:     (usize, usize),
    category: String
}
...
fn main {
    ...
    println!("book2 = {:?}", book2.clone());
}
book2 = Book { isbn: "000-0-000000-00-1", size: (30, 50), category: "Python" }

内部可変性

ある構造体の一部のフィールドのみを可変にした性質を内部可変性というそうです。この内部可変性を実現するために、Rustではstdd::cellにCellとRefCellが用意されています。
Cellは、T型の値を一つ持ち、mutな参照を持っていなくても、値を読み書きできます。ただし、Cellのもつ値はCopyトレイトを実装した値のみ対応しています。
RefCellもT型の値を一つ持ち、mutaな参照を借用します。こちらは、値にCopyトレイトを実装していなくても良いです。ただし、複数の借用を同じスコープ内で行った場合、コンパイル時のエラーではなく、実行時のpanicを起こします。
これらは、スレッド安全ではないそうなので、マルチスレッドでは使えないようです。

PublicPrivateBook構造体を内部可変性にしてみた例を以下に示します。

mod module_book {
    // public struct and private field
    pub struct PublicPrivateBook {
        id:       std::cell::Cell<i32>,
        isbn:     std::cell::RefCell<String>,
        size:     (usize, usize),
        category: String
    }
    impl PublicPrivateBook {
        pub fn new() -> PublicPrivateBook {
            PublicPrivateBook {
                id: std::cell::Cell::new(0),
                isbn: std::cell::RefCell::new("000-0-000000-00-0".to_string()),
                size: (0, 0),
                category: "".to_string()
            }
        }
        pub fn new2() -> Self {
            PublicPrivateBook {
                id: std::cell::Cell::new(0),
                isbn: std::cell::RefCell::new("000-0-000000-00-0".to_string()),
                size: (0, 0),
                category: "".to_string()
            }
        }
        pub fn show(&self) {
            println!("book = id: {}, isbn: {:?}, size: {:?}, category: {}", self.id.get(), self.isbn.borrow(), self.size, self.category);
        }
        pub fn id(&self, id: i32) {
            self.id.set(id);
        }
        pub fn isbn(&self, isbn: &str) {
            self.isbn.replace(isbn.to_string());
        }
    }
}
...

isbnメソッドではselfをmutで借りることなくisbnの値を変更しています。

ソース

// private struct and field
#[derive(Clone, Debug)]
struct Book {
    isbn:     String,
    size:     (usize, usize),
    category: String
}
mod module_book {
    // public struct and field
    pub struct PublicBook {
        pub isbn:     String,
        pub size:     (usize, usize),
        pub category: String
    }
    // public struct and private field
    pub struct PublicPrivateBook {
        id:       std::cell::Cell<i32>,
        isbn:     std::cell::RefCell<String>,
        size:     (usize, usize),
        category: String
    }
    impl PublicPrivateBook {
        pub fn new() -> PublicPrivateBook {
            PublicPrivateBook {
                id: std::cell::Cell::new(0),
                isbn: std::cell::RefCell::new("000-0-000000-00-0".to_string()),
                size: (0, 0),
                category: "".to_string()
            }
        }
        pub fn new2() -> Self {
            PublicPrivateBook {
                id: std::cell::Cell::new(0),
                isbn: std::cell::RefCell::new("000-0-000000-00-0".to_string()),
                size: (0, 0),
                category: "".to_string()
            }
        }
        pub fn show(&self) {
            println!("book = id: {}, isbn: {:?}, size: {:?}, category: {}", self.id.get(), self.isbn.borrow(), self.size, self.category);
        }
        pub fn id(&self, id: i32) {
            self.id.set(id);
        }
        pub fn isbn(&self, isbn: &str) {
            self.isbn.replace(isbn.to_string());
        }
    }
}

struct BookSize((usize, usize), (usize, usize), (usize, usize));

struct UnitBook;

struct GenericBook<T> {
    book: module_book::PublicPrivateBook,
    seal: Vec<T>
}
impl<T> GenericBook<T> {
    pub fn new() -> Self {
        GenericBook {book: module_book::PublicPrivateBook::new(), seal: Vec::new()}
    }
    pub fn seal(&mut self, t:T) {
        self.seal.push(t);
    }
    pub fn show(&self) {
        self.book.show();
        // println!("seal = {:?}", self.seal); // Error: `T` doesn't implement `std::fmt::Debug`
    }
}

fn main() {
    let book1 = Book {
        isbn: "000-0-000000-00-0".to_string(),
        size: (20, 40),
        category: "Rust".to_string()
    };
    let isbn = "000-0-000000-00-1".to_string();
    let size = (30, 50);
    let category = "Python".to_string();
    let book2 = Book {isbn, size, category};
    // let book3 = Book {size, category}; // Error: missing isbn
    println!("book1 = isbn: {}, size: {:?}, category: {}", book1.isbn, book1.size, book1.category);
    println!("book2 = isbn: {}, size: {:?}, category: {}", book2.isbn, book2.size, book2.category);
    let book3 = module_book::PublicBook {
        isbn: "000-0-000000-00-2".to_string(),
        size: (20, 40),
        category: "C".to_string()
    };
    println!("book3 = isbn: {}, size: {:?}, category: {}", book3.isbn, book3.size, book3.category);
    // let book4 = module_book::PublicPrivateBook { // Error: field is private
    //     isbn: "000-0-000000-00-3".to_string(),
    //     size: (20, 40),
    //     category: "C++".to_string()
    // };
    let book5 = Book{size: (10, 10), ..book1}; // almost the same structure
    println!("book5 = isbn: {}, size: {:?}, category: {}", book5.isbn, book5.size, book5.category);

    let book_size = BookSize(book1.size, book2.size, book5.size);
    println!("book_size = {:?}, {:?}, {:?}", book_size.0, book_size.1, book_size.2);

    let mut book4 = module_book::PublicPrivateBook::new();
    book4.isbn("000-0-000000-00-3");
    // println!("book4 = isbn: {}, size: {:?}, category: {}", book4.isbn, book4.size, book4.category); // Error: field is private
    book4.show();
    let mut book6 = module_book::PublicPrivateBook::new2();
    book6.id(5);
    book6.isbn("000-0-000000-00-5");
    book6.show();
    let mut generic_book = GenericBook::<String>::new();
    generic_book.seal("abc".to_string());
    generic_book.show();
    println!("book2 = {:?}", book2.clone());
}
book1 = isbn: 000-0-000000-00-0, size: (20, 40), category: Rust
book2 = isbn: 000-0-000000-00-1, size: (30, 50), category: Python
book3 = isbn: 000-0-000000-00-2, size: (20, 40), category: C
book5 = isbn: 000-0-000000-00-0, size: (10, 10), category: Rust
book_size = (20, 40), (30, 50), (10, 10)
book = id: 0, isbn: "000-0-000000-00-3", size: (0, 0), category:
book = id: 5, isbn: "000-0-000000-00-5", size: (0, 0), category:
book = id: 0, isbn: "000-0-000000-00-0", size: (0, 0), category:
book2 = Book { isbn: "000-0-000000-00-1", size: (30, 50), category: "Python" }

今回はここまで!
構造体を学びました。これで、非常に値を扱いやすくなりましたね。

14
6
1

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
14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?