Rust dojo第47回を開催した

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

kdnakt.hatenablog.com

 

 

[第47回の様子]

2022/07/13に第47回を開催した。

 

内容としてはRust By Example 日本語版のライフタイムの章「15.4.8. スタティックライフタイム」に取り組んだ。

 

参加者は自分を入れて4人。

そういえばRust dojoが始まって1年くらい経った気がする。最初は10人ちょっといたけど、半分は残らなかったな...。それでも一緒に毎週手を動かしてくれる同僚がいるのはありがたい。

 

[学んだこと]

  • 15.4.8. スタティックライフタイム
  • これまでは'aとか'bというジェネリクスでライフタイムのラベルづけをしていた
  • しかし、予約されているライフタイム名がある:その一つが'static
  • 'staticなライフタイムの場合、プログラムのライフタイムと同じになる
    • ただし、他のライフタイム同様に圧縮が可能
  • 'staticなライフタイムの変数を作る方法は2つ
  • static宣言で定数を作成する
static NUM: i32 = 18;

fn main() {
    println!("NUM: {}", NUM); // NUM: 18
}
  • もう一つが文字列リテラル で&'static str型にする
fn main() {
    {
        // 型を明示する場合以下のようになる
        // let static_string: &'static str = "I'm in read-only memory"
    
        // 省略するとこうなる
        let static_string = "I'm in read-only memory";
        println!("static_string: {}", static_string);

        // static_string: I'm in read-only memoryが出力される
    }
    // スコープを抜けたのでstatic_string変数は利用できなくなる
    // しかし、文字列のデータはバイナリ中に残っている
}
  • 次に、'staticライフタイムの圧縮について見ていく
  • 関数経由で'staticなライフタイムを圧縮した場合でも、元々の変数自体にはアクセスできる
// staticなライフタイムの変数
static NUM: i32 = 18;

// NUMのライフタイムを'staticから'aにする関数
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}

fn main() {
    {
        let lifetime_num = 9;

        // このスコープのライフタイムに圧縮されているNUMへの参照
        let coerced_static = coerce_static(&lifetime_num);

        println!("coerced_static: {}", coerced_static);
        // coerced_static: 18
    }

    // NUM自体のライフタイムはstaticなままなのでアクセスできる
    println!("NUM: {} stays accessible!", NUM);
    // NUM: 18 stays accessible!
}
  • ここでわざわざライフタイムのある変数を作るのが違和感があるな(9に意味があるわけではないので)と思っていたら、こういう時に以前使ったPhantomData型(幽霊型)を使えるとのアドバイスをもらった
  • 実際にPhantomData型を使ってみると次のようになる👻
use std::marker::PhantomData;

// staticなライフタイムの変数
static NUM: i32 = 18;

// NUMのライフタイムを'staticから'aにする関数
fn coerce_static<'a>(_: PhantomData<&'a i32>) -> &'a i32 {
    &NUM
}

fn main() {
    {
        let lifetime_phantom = PhantomData;

        let coerced_static = coerce_static(lifetime_phantom);

        println!("coerced_static: {}", coerced_static);
        // coerced_static: 18
    }

    println!("NUM: {} stays accessible!", NUM);
    // NUM: 18 stays accessible!
}
  • トレイト境界で'staticが指定されている場合、「その型が'staticでない参照を含まない」ということ
  • 値がコピーされる場合にはこの条件を満たす
use std::fmt::Debug;

fn print_it( input: impl Debug + 'static ) {
    println!( "'static value passed in is: {:?}", input );
}

fn main() {
    let i = 5;
    print_it(i);
}
  • しかし、参照の場合は、呼び出しもとの関数のライフタイムと同じになるためstaticでない:よってコンパイルエラーとなってしまう
use std::fmt::Debug;

fn print_it( input: impl Debug + 'static ) {
    println!( "'static value passed in is: {:?}", input );
}

fn main() {
    let i = 5;
    print_it(&i);
}

// 以下のコンパイルエラー
error[E0597]: `i` does not live long enough
  --> src/lib.rs:15:15
   |
15 |     print_it(&i);
   |     ---------^^--
   |     |         |
   |     |         borrowed value does not live long enough
   |     argument requires that `i` is borrowed for `'static`
16 | }
   | - `i` dropped here while still borrowed
  • 参照だと常にダメかというと、staticな変数への参照の場合には問題がない
use std::fmt::Debug;

// staticな定数
static J: i32 = 42;

fn print_it( input: impl Debug + 'static ) {
    println!( "'static value passed in is: {:?}", input );
}

fn main() {
    let static_int = &J;
    print_it(static_int);
    // 'static value passed in is: 42と出力される
}

 

[まとめ]

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

ライフタイム難しすぎワロタ...1ページしか進まなかった(しかもあんまり理解できてない)。

 

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

github.com