前回、可変参照は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)ライフタイム注釈
明確にコンパイラにライフタイムを伝えないといけない場合もあります。
静的解析ができない、動的に決定が必要な場合です。
このような場合は、ライフタイムを明示的に示すライフタイム注釈が必要な場合があります。
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)シャドーイングで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)- こちらの解説が詳しいです。 ↩︎
- ちょっと使い方がわかりませんでした。その他、RustRoverに可視化機能があるようですが試していません。また、VSCodeのエクステンションもあるRustOwlで可視化は可能です。 ↩︎
- このあたりでしょうか。 ↩︎
第1部 TOC
- 準備
- ターミナル入出力を試す
- ターミナル系crateを利用する
- 編集データと表示処理
- 画面制御
- 編集データへの入出力
- その他
GitHubにコードをアップロードしています。0.1.0バージョン
コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。
# Example
$ cargo run --example first_stepCode language: Bash (bash)
