Rust dojo第45回を開催した

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

kdnakt.hatenablog.com

 

 

[第45回の様子]

2022/06/29に第45回を開催した。

 

内容としてはRust By Example 日本語版のライフタイムの章「15.4.2. 関数」、「15.4.3. メソッド」、「15.4.4. 構造体」、「15.4.5. Traits」に取り組んだ。

 

参加者は自分を入れて確か5人。

継続的に参加してくれる皆様に感謝。

 

[学んだこと]

  • 15.4.2. 関数
  • いきなりライフタイム省略の話が出てきたけど、この後の章で説明される部分なので読み飛ばす...
  • ライフタイムのシグネチャを持つ関数は以下の制約がある
    • 全ての変数のライフタイムを明示する必要がある
    • 返り値の参照のライフタイムは引数と同じかstatic
// 引数が1つ
fn print_one<'a>(x: &'a i32) {
    println!("`print_one`: x is {}", x);
}

// 引数が2つ
fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("`print_multi`: x is {}, y is {}", x, y);
}

// 受け取った参照をそのまま同じライフタイムで返す
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }
  • 関数スコープの変数を返すとライフタイムが足りずコンパイルエラーとなる
// Stringは関数の終了後破棄されるため、'aのライフタイムより短い
fn invalid_output<'a>() -> &'a String { &String::from("foo") }

// よって下記のコンパイルエラー
error[E0515]: cannot return reference to temporary value
  --> src/main.rs:31:41
   |
31 | fn invalid_output<'a>() -> &'a String { &String::from("foo") }
   |                                         ^-------------------
   |                                         ||
   |                                         |temporary value created here
   |                                         returns a reference to data owned by the current function
struct Owner(i32);

impl Owner {
    // 通常の関数と同様ライフタイムを明示
    fn add_one<'a>(&'a mut self) { self.0 += 1; }
    fn print<'a>(&'a self) {
        println!("`print`: {}", self.0);
    }
}
  • 15.4.4. 構造体
  • 構造体のライフタイムも関数と似ている
  • 参照は構造体よりも長いライフタイムが必要となる
// `i32`への参照をメンバに持つ`Borrowed`型。
// 参照は`Borrowed`自体よりも長生きでなくてはならない。
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

fn main() {
    let x = 18;

    let single = Borrowed(&x);

    println!("x is borrowed in {:?}", single);
    // x is borrowed in Borrowed(18)
}
#[derive(Debug)]
enum Either<'a> {
    Num(i32),
    Ref(&'a i32),
}

fn main() {
    let x = 18;
    let y = 15;

    let reference = Either::Ref(&x);
    let number    = Either::Num(y);

    println!("x is borrowed in {:?}", reference);
    // x is borrowed in Ref(18)

    println!("y is *not* borrowed in {:?}", number);
    // y is *not* borrowed in Num(15)
}
  • 15.4.5. Traits
  • トレイトの実装に際してもライフタイムを明示することができる
#[derive(Debug)]
struct Borrowed<'a> {
    x: &'a i32,
}

impl<'a> Default for Borrowed<'a> {
    fn default() -> Self {
        Self {
            x: &10,
        }
    }
}

fn main() {
    let b: Borrowed = Default::default();
    println!("b is {:?}", b);
    // b is Borrowed { x: 10 }
}
  • ここで不思議なのは、Self { x: &10 }の部分:10のライフタイムはdefault()の中に閉じるからライフタイムが短いのでは?と思ったがどうもそうではないらしい。
  • 10の所有権がdefault()ではなくSelfのほうにあるから?
  • 試しに以下のように一度変数にすると、やはりライフタイムが不足する
impl<'a> Default for Borrowed<'a> {
    fn default() -> Self {
        let ten = 10;
        Self {
            x: &ten,
        }
    }
}

// 以下のコンパイルエラー
error[E0515]: cannot return value referencing local variable `ten`
  --> src/main.rs:11:9
   |
11 | /         Self {
12 | |             x: &ten,
   | |                ---- `ten` is borrowed here
13 | |         }
   | |_________^ returns a value referencing data owned by the current function
  • 以下のように実験してみると、Self { x: &10 }の10が静的定数の領域に存在していそうなことがわかる
struct A(i32);

fn f() -> &'static A { &A(100) }

fn g<'a>() -> &'a A { &A(100) }

fn h<'a>() -> &'a A { &A(200) }

const CONSTANT_A: A = A(100);    // Global constant of A(100)

fn main() {
    let p_f = f() as *const A;
    
    // f() と &a は同じアドレス
    println!("{:?}", p_f == &CONSTANT_A); // true 
    
    // f() と g() は同じアドレス
    println!("{:?}", p_f == g()); // true
    
    // f() と h() は異なるアドレス
    println!("{:?}", p_f == h()); // false
}

 

[まとめ]

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

ライフタイム...むずかしい...staticとかグローバルなconstのメモリ位置が普通の変数と違うっぽいのはなんとなく分かってきた。

 

プルリクエストはこちら。

github.com