Rust dojo第32回を開催した

第32回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第32回の様子]

2022/03/23に第32回を開催した。

 

内容としてはRust By Example 日本語版の「10.2. 構造体の場合」、「10.3. use宣言」、「10.4. super と self」に取り組んだ。

 

参加者は5人。いつものメンバーが大体集まってくれた。(あれ、6人だったっけ...?)

ただ、最初に集まったメンバー3人の中では自分が一番ドライバをやっていなかったので、自分がドライバを担当した。

 

[学んだこと]

  • 10.2. 構造体の場合
  • 先週はモジュールに含まれる関数とたわむれた。今週はモジュールに含まれる構造体。
  • 関数と同じく、構造体もデフォルトはプライベート、かつ構造体のフィールドもプライベート。なのでアクセスする場合は以下のようにpub修飾子をつける必要がある
mod my {
    pub struct OpenBox<T> {
        pub contents: T,
    }
}

fn main() {
    // モジュールの構造体にアクセス
    let open_box = my::OpenBox { contents: "public information" };
    // モジュールの構造体のフィールドにアクセス
    println!("The open box contains: {}", open_box.contents);
}
  • デフォルトはフィールドがプライベートなので、その場合は構造体を初期化できない:前回までのprivate functionとかprivate moduleと同様、private fieldというエラーメッセージが表示される
mod my {
    pub struct ClosedBox<T> {
        contents: T,
    }
}

fn main() {
    let closed_box = my::ClosedBox { contents: "classified information" };
}

// 以下のコンパイルエラー
error[E0451]: field `contents` of struct `ClosedBox` is private
  --> src/main.rs:41:38
   |
41 |     let closed_box = my::ClosedBox { contents: "classified information" };
   |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ private field
  • このような場合でも、モジュール内からはプライベートな要素にアクセスできるので、以下のようにコンストラクタを経由すれば問題ない
mod my {
    pub struct ClosedBox<T> {
        contents: T,
    }

    impl<T> ClosedBox<T> {
        pub fn new(contents: T) -> ClosedBox<T> {
            ClosedBox {
                contents: contents,
            }
        }
    }
}

fn main() {
    let _closed_box = my::ClosedBox::new("classified information");
}
  • ただ、それでは結局構造体のフィールドにあるデータを利用できないので、データをプリントする関数を実装してみる
mod my {
    pub struct ClosedBox<T> {
        contents: T,
    }

    impl<T> ClosedBox<T> {
        pub fn new(contents: T) -> ClosedBox<T> {
            ClosedBox {
                contents: contents,
            }
        }
        
        // データをプリントする関数を追加
        pub fn print(&self) {
            println!("The closed box contains: {}", &self.contents);
        }
    }
}

fn main() {
    let closed_box = my::ClosedBox::new("classified information");
    closed_box.print();
}

// 以下のコンパイルエラー
error[E0277]: `T` doesn't implement `std::fmt::Display`
  --> src/main.rs:25:53
   |
25 |             println!("The closed box contains: {}", &self.contents);
   |                                                     ^^^^^^^^^^^^^^ `T` cannot be formatted with the default formatter
   |
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
   |
15 |     impl<T: std::fmt::Display> ClosedBox<T> {
   |           +++++++++++++++++++
  • T型がstd::fmt::Display型を実装していないので、printlnマクロでフォーマットできない、と言われてしまった
  • ただ、修正方法としてimpl<T: std::fmt::Display> ClosedBox<T>とトレイト境界を明示すれば良いと書かれているので、実践してみる
mod my {
    pub struct ClosedBox<T> {
        contents: T,
    }

    impl<T std::fmt::Display> ClosedBox<T> {
        pub fn new(contents: T) -> ClosedBox<T> {
            ClosedBox {
                contents: contents,
            }
        }
        
        // データをプリントする関数を追加
        pub fn print(&self) {
            println!("The closed box contains: {}", &self.contents);
        }
    }
}

fn main() {
    let closed_box = my::ClosedBox::new("classified information");
    closed_box.print();
    // The closed box contains: classified informationが出力される
}
  • 別解として、内部のフィールドに対するゲッターを提供するパターンも実装してみた
mod my {
    pub struct ClosedBox2<T> {
        contents: T,
    }
    impl<T> ClosedBox2<T> {
        pub fn new(contents: T) -> ClosedBox2<T> {
            ClosedBox2 {
                contents: contents,
            }
        }
        pub fn getContents(&self) -> &T {
            &self.contents
        }
    }
}

fn main() {
    let closed_box2 = my::ClosedBox2::new("classified information 2");
    println!("The closed box 2 contains: {}", closed_box2.getContents());
    // The closed box 2 contains: classified information 2が出力される
}
  • この場合は、T型のトレイト境界としてstd::fmt::Display型を明示しなくても良い
  • おそらくコンストラクタに渡されているstr型が利用されるからだろう
  • 10.3. use宣言
  • use宣言をすると、長い名前のモジュールを新しい名前にバインドできる
// deeplyクレートのnestedモジュールのmy_first_function関数を使うと定義する
use crate::deeply::nested::{
    my_first_function
};

fn main() {
    // useで宣言しているのでクレート名やモジュール名を省略して利用できる
    my_first_function();
}
  • asキーワードで別名にバインドできる
use deeply::nested::function as other_function;

mod deeply {
    pub mod nested {
        pub fn function() {
            println!("called `deeply::nested::function()`");
        }
    }
}

fn main() {
    other_function();
    // called `deeply::nested::function()`が出力される
}
  • { }でブロックを作りその中でuseを利用すると、同名の関数を隠す(シャドウイング)ことができる
fn function() {
    println!("called `function()`");
}

mod deeply {
    pub mod nested {
        pub fn function() {
            println!("called `deeply::nested::function()`");
        }
    }
}

fn main() {
    println!("Entering block");
    {
        use crate::deeply::nested::function;
        function();
        // called `function()`ではなく、
        // called `deeply::nested::function()`が出力される
        println!("Leaving block");
    }
    function(); // called `function()`が出力される
}
  • 10.4. super と self
  • 10.1. パブリックとプライベートで登場した、superやselfの解説
  • selfは現在のモジュールスコープを指すので、以下の場合は同じ関数を呼び出していることになる
mod my {
    fn function() {
        println!("called `my::function()`");
    }
    
    pub fn indirect_call() {
        // この2つは同じ関数を呼び出している
        self::function();
        function();
    }    
}

fn main() {
    my::indirect_call();
}
  • superを使うと親スコープを参照するので、myモジュールの外側の関数が呼び出される
fn function() {
    println!("called `function()`");
}

mod my {

    pub fn indirect_call() {
        super::function();
        // called `function()`が出力される
    }
}

fn main() {
    my::indirect_call();
}
  • crateスコープを利用すると、一番外側のスコープを参照できる
mod cool {
    pub fn function() {
        println!("called `cool::function()`");
    }
}

mod my {
    mod cool {
        pub fn function() {
            println!("called `my::cool::function()`");
        }
    }
    
    pub fn indirect_call() {
        // いずれも"called `my::cool::function()`"が出力される
        self::cool::function();
        cool::function();
        
        {
            use crate::cool::function as root_function;
            root_function(); // "called `cool::function()`"が出力される
        }
    }
}

fn main() {
    my::indirect_call();
}

 

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。

Rustの型にハマった回でした。難しい...

 

今週のプルリクエストはこちら(余計なバイナリが混ざってたorz)。

github.com