開発

[binllion] Rustの借用のライフタイムを理解する

前回、可変参照は1つのみなのでN太はなにもできない、となっていましたがライフタイムを考慮すれば別の世界線も見えてきます。

借用のライフタイムを元に組み替えた場合を見てみましょう。

続:可変参照

S夫

わるいなN太

この&mutはひとりようなんだ


N太

そんな~!!!


しZちゃん

いつもかわいそうなN太さん

でも、だいじょうぶよ!

NLLに気をつけて食べればいいわ!


N太

ありがとう、しZちゃん!

(大量の涙と鼻水)


S夫

ちぇ、つまんないの!


変数のスコープとは違い参照値の有効範囲はその意味的な範囲となります。つまり値が使われなくなったらライフタイムが終了します。これがNLLです。

この実質的ライフタイムに気をつけて組み直せば前回のコードも下記のように実行できるようになります。

ただしコンパイルできたからといって論理的なエラーがないとはならないので、やりたいことができているのかどうか注意が必要です。

NLLとはNon-Lexical Lifetimesの略になります1。制御フロー を元にした判定のためライフタイムの可視化はされておらず難易度を上げている一因です。

可視化するツールとしてRustVizがあるようですが動作するのかわかりません2

2024 editionではこのあたりの事情がもう少し変化してくるかもしれません3

fn main() {
    let mut s夫 = String::from("有名店の高級チョコレート");
    println!("{:} ", s夫); // 不変参照

    let しzちゃん = &mut s夫;  // 可変参照
    *しzちゃん = "はんぶんなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", *しzちゃん); // これ以降しzちゃんは使われなくなった

    let n太 = &mut s夫;  // しzちゃんは使われていないので可変参照OK
    *n太 = "のこりもなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", *n太); // これ以降n太は使われなくなった

    s夫 = "なにもなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", s夫); // 不変参照だがもう可変参照は使われていないのでOK
}
Code language: JavaScript (javascript)

Playground

ライフタイム注釈

明確にコンパイラにライフタイムを伝えないといけない場合もあります。

静的解析ができない、動的に決定が必要な場合です。

このような場合は、ライフタイムを明示的に示すライフタイム注釈が必要な場合があります。

The Rust Programming Languageにて解説されていますがイマイチわかりづらいのでこちらこちらを参考にすると良いと思います。

シャドーイング

借用のライフタイムと直接関係ありませんが、シャドーイングを使っていると間違えて古い変数を参照し続けて意図しない動きとなるかもしれません。

シャドーイング前の変数はスコープが有効な間生きているようなのでコンパイルできてしまいます。

スナップショット的な用途を想定したものでしょうか?おそらくシャドーイング前の変数の内容を参照するようなケースのために即座に破棄されないのでしょう(let foo = fooのようなケース)。

特に他の人のコードをいじるときは注意が必要です。

fn main() {
    let mut foo = String::from("abcdef");
    let  old_foo_ref = &mut foo;
    println!("{:p} ", old_foo_ref);
    println!("{:} ", *old_foo_ref);

    let foo = String::from("123456");  // fooの内容をシャドーイングで書き換え
    let new_foo_ref = & foo;

    println!("After shadowing...");
    println!("{:p} ", old_foo_ref); // 古い内容を参照していてもコンパイル時に警告は出ない
    println!("{:} ", *old_foo_ref); // 古い内容を参照していてもコンパイル時に警告は出ない

    println!("bar_ref...");
    println!("{:p} ", new_foo_ref);
    println!("{:} ", *new_foo_ref);
}

出力例
0x7fffa6ffbc78 
abcdef 
After shadowing...
0x7fffa6ffbc78 
abcdef 
bar_ref...
0x7fffa6ffbd18 
123456 Code language: JavaScript (javascript)

Playground

シャドーイングでNLLをコントロール

意味的な範囲で解釈される、という点を付けばシャドーイングを駆使してコードを最小限の変更で修正することもできます。

下記は前回サンプルコードですがコメントを外すと動かなくなります。

このコードにシャドーイングのコードを追加すればコメントを外しても実行できるようになります。

勿論可読性が下がりますし、warningも出るのでオススメはしません。

fn main() {
    let mut s夫 = String::from("有名店の高級チョコレート");
    let しzちゃん = &mut s夫;
    // let n太 = &mut s夫; // 可変参照は2つ同時に作れないのでエラー
    // let n太 = &s夫; // 可変参照と不変参照は2つ同時に作れないのでエラー

    // println!("{:} ", s夫); // 可変参照と不変参照はどちらか1つしか同時に作れないのでエラー
    *しzちゃん = "はんぶんなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", *しzちゃん);

    // println!("{:} ", *n太);  // 参照が作れなかったので実行できない

    s夫 = "のこりもなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", s夫);
}Code language: JavaScript (javascript)
fn main() {
    let mut s夫 = String::from("有名店の高級チョコレート");
    let しzちゃん = &mut s夫;
    let n太 = &mut s夫; // 可変参照は2つ同時に作れないのでエラー
    let n太 = &s夫; // 可変参照と不変参照は2つ同時に作れないのでエラー

    println!("{:} ", s夫); // 可変参照と不変参照はどちらか1つしか同時に作れないのでエラー
    let しzちゃん = &mut s夫; // シャドーイングを追加
    *しzちゃん = "はんぶんなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", *しzちゃん);

    let n太 = & s夫; // シャドーイングを追加
    println!("{:} ", *n太);  // 参照が作れなかったので実行できない

    s夫 = "のこりもなくなった有名店の高級チョコレート".to_string();
    println!("{:} ", s夫);
}Code language: JavaScript (javascript)

Playground


  1. こちらの解説が詳しいです。 ↩︎
  2. ちょっと使い方がわかりませんでした。その他、RustRoverに可視化機能があるようですが試していません。また、VSCodeのエクステンションもあるRustOwlで可視化は可能です。 ↩︎
  3. このあたりでしょうか。 ↩︎

GitHubにコードをアップロードしています。0.1.0バージョン

コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。

# Example
$ cargo run --example first_stepCode language: Bash (bash)
管理人

Recent Posts

情報セキュリティマネジメント試験取得への道

スキルアップを図るべく情報セキ…

2か月 ago

ファイナンシャルプランナー3級試験取得への道

スキルアップを図るべくファイナ…

2か月 ago

[rust] New Type Patternを使ってみる

DDDの考えを取り入れることで…

5か月 ago

RustでDDDの要素を取り入れてみる

前回SOLID原則というものを…

5か月 ago

期間限定!書籍無料キャンペーン2025

「mdBookではじめるKin…

5か月 ago