前回、可変参照は1つのみなのでN太はなにもできない、となっていましたがライフタイムを考慮すれば別の世界線も見えてきます。
借用のライフタイムを元に組み替えた場合を見てみましょう。
続:可変参照
S夫
わるいなN太
この&mutはひとりようなんだ
N太
そんな~!!!
しZちゃん
いつもかわいそうなN太さん
でも、だいじょうぶよ!
NLLに気をつけて食べればいいわ!
N太
ありがとう、しZちゃん!
(大量の涙と鼻水)
S夫
ちぇ、つまんないの!
変数のスコープとは違い参照値の有効範囲はその意味的な範囲となります。つまり値が使われなくなったらライフタイムが終了します。
この実質的ライフタイムに気をつけて組み直せば前回のコードも下記のように実行できるようになります。
ただしコンパイルできたからといって論理的なエラーがないとはならないので、やりたいことができているのかどうか注意が必要です。
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;
println!("{:} ", *n); // ここでnは使われなくなった
println!("{:} ", s); // 不変参照だがもう可変参照は使われていないのでOK
}
ライフタイム注釈
明確にコンパイラにライフタイムを伝えないといけない場合もあります。
静的解析ができない動的に決定が必要な場合です。
このような場合は、ライフタイムを明示的に示すライフタイム注釈が必要な場合があります。
The Rust Programming Languageにて解説されていますがイマイチわかりづらいのでこちらやこちらを参考にすると良いと思います。
シャドーイング
借用のライフタイムと直接関係ありませんが、シャドーイングを使っていると間違えて古い変数を参照し続けて意図しない動きとなるかもしれません。
シャドーイング前の変数はスコープが有効な間生きているようなのでコンパイルできてしまいます。
スナップショット的な用途を想定したものでしょうか?おそらくシャドーイング前の変数の内容を参照するようなケースのために即座に破棄されないのでしょう(この例にあるようにlet foo = fooのようなケース)。
特に他の人のコードをいじるときは注意が必要です。
fn main() {
let mut foo = String::from("abcdef");
let foo_ref = &mut foo;
println!("{:p} ", foo_ref);
println!("{:} ", *foo_ref);
let foo = String::from("123456");
let bar_ref = & foo;
println!("After shadowing...");
println!("{:p} ", foo_ref); // 古い内容を参照していてもコンパイル時に警告は出ない
println!("{:} ", *foo_ref); // 古い内容を参照していてもコンパイル時に警告は出ない
println!("bar_ref...");
println!("{:p} ", bar_ref);
println!("{:} ", *bar_ref);
}
出力例
0x7fffa6ffbc78
abcdef
After shadowing...
0x7fffa6ffbc78
abcdef
bar_ref...
0x7fffa6ffbd18
123456
TOC
GitHubにコードをアップロードしています。
コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。
# Example
$ cargo run --examples first_step