【Rust】変数のスコープまとめ
Rustの特徴的な言語仕様として、「所有権とライフタイム」というのがあります。
これと上手に付き合うには、「この変数がいつまで生きているのか」つまり「変数のスコープ」を知ることがとても重要です。これがわからないと、Rustのボローチェッカーに頻繁に指摘されてもどうすればいいのかわからなかったりするでしょう。
Rustで躓くポイントの一つだと思います。
そこでこの記事では、「変数のスコープ」について、極力具体的なコードを挙げて掘り下げていきたいと思います。
その前に、変数のスコープを確認しやすくするために、以下のような定義の構造体を定義して、生成と破棄のタイミングで標準出力されるようにします。
struct MyStruct<T: Display> {
value: T,
}
impl<T: Display> MyStruct<T> {
fn new(value: T) -> Self {
// MyStructが生成されるタイミングで実行
println!("new {}", value);
Self { value }
}
fn value(&self) -> &T {
&self.value
}
}
impl<T: Display> Drop for MyStruct<T> {
fn drop(&mut self) {
// MyStructが破棄されるタイミングで実行
println!("drop {}", &self.value);
}
}
以降、MyStruct
構造体は、この構造体を使用しているものとします。
基本
関数のスコープ
関数内に所有権がある変数は、関数を抜けるタイミングで破棄されます。
下記の例では、"drop A"が"end main"よりも先に出力されていることからわかります。
fn my_fn() {
println!("start my_fn");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
println!("end my_fn");
// drop A
}
println!("start main");
my_fn();
println!("end main");
start main
start my_fn
new A
use A
end my_fn
drop A
end main
そのため、関数内に所有権がある変数の参照は、関数外に出せません。
fn my_fn<'a>() -> &'a String {
println!("start my_fn");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
println!("end my_fn");
a.value()
// drop A
}
error[E0515]: cannot return reference to local variable `a`
--> src\main.rs:14:5
|
14 | a.value()
| ^^^^^^^^^ returns a reference to data owned by the current function
ブロックのスコープ
スコープを定義したい場合は、{}
でブロックを作ると、その内部が変数のスコープになります。この時、ブロック内に所有権がある変数は、ブロックを抜けるタイミングで破棄されます。
下記の例では、"drop A"が"end main"よりも先に出力されていることからわかります。
println!("start main");
{
println!("start block");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
println!("end block");
// drop A
}
println!("end main");
start main
start block
new A
use A
end block
drop A
end main
そのため、ブロック内に所有権がある変数の参照は、ブロック外に出せません。
println!("start main");
let a = {
println!("start block");
let a = MyStruct::new("A".to_string()); // new A
println!("use1 {}", a.value());
println!("end block");
a.value()
// drop A
};
println!("use2 {}", a);
println!("end main");
error[E0597]: `a` does not live long enough
--> src\main.rs:10:9
|
5 | let a = {
| - borrow later stored here
...
10 | a.value()
| ^^^^^^^^^ borrowed value does not live long enough
11 | };
| - `a` dropped here while still borrowed
ループのスコープ
ループ中に作成した変数はそのループが完了すると、破棄されます。
下記の例では、出力結果から、次のループが始まる前にdropされていることがわかります。
println!("start main");
let mut i = 0;
loop {
println!("start loop");
let a = MyStruct::new("A".to_string() + &i.to_string()); // new A
println!("use {}", a.value());
if i < 2 {
i += 1;
} else {
break;
}
println!("end loop");
// drop A
}
println!("end main");
start main
start loop
new A0
use A0
end loop
drop A0
start loop
new A1
use A1
end loop
drop A1
start loop
new A2
use A2
drop A2
end main
宣言順によるスコープ差
後に宣言された変数から先に破棄されます。引数は後ろから先に破棄されます。
fn my_fn(arg1: MyStruct<String>, arg2: MyStruct<String>) {
println!("start my_fn");
let a = MyStruct::new("A".to_string());
let b = MyStruct::new("B".to_string());
println!("end my_fn");
}
my_fn(MyStruct::new("ARG1".to_string()), MyStruct::new("ARG2".to_string()));
new ARG1
new ARG2
start my_fn
new A
new B
end my_fn
drop B
drop A
drop ARG2
drop ARG1
その他、構造体の宣言の後ろ順から破棄されるなど、細かいルールが決まっています。そのへんは
あたりに載っているので、そちらを参照してください。一時的なスコープ
一時変数(右辺値)のスコープ
// let a = MyStruct::new("A".to_string());
MyStruct::new("A".to_string());
のように、let
で変数を束縛しない場合、生成されたけど束縛されなかった一時変数は、生成された式が終わった時点で破棄されます。
下記の例では、"drop A"が"end main"よりも先に出力されていることからわかります。
println!("start main");
println!("use {}", MyStruct::new("A".to_string()).value()); // new A drop A
println!("end main");
start main
new A
use A
drop A
end main
以下はlet
で束縛した場合です。"end main"が"drop A"よりも先に出力されているので、main関数終了まで破棄されていないことがわかります。
println!("start main");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
println!("end main");
// drop A
start main
new A
use A
end main
drop A
生成されたけど束縛されなかった一時変数は、生成された式が終わった時点で破棄されるため、一時変数の参照を使用することはできません。
println!("start main");
let a: &String = MyStruct::new("A".to_string()).value(); // new A drop A
println!("use {}", a); // error MyStructのdrop後に、MyStruct::valueの参照を使用している!!
println!("end main");
error[E0716]: temporary value dropped while borrowed
--> src\main.rs:6:22
|
6 | let a: &String = MyStruct::new("A".to_string()).value();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
7 | println!("use {}", a);
| - borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
エラーメッセージに"temporary value"の文言が出てきたら、この辺の話だと思ってください。
また、あまり重要ではありませんが、let _
で受けた場合は、束縛した扱いされず、一時変数として即時破棄されます。
println!("start main");
let _ = MyStruct::new("A".to_string());
println!("end main");
start main
new A
drop A
end main
let _a
等、アンダースコアで始まる変数名で受けた場合は、通常のlet
同様にスコープを抜けるまで破棄されません。
println!("start main");
let _a = MyStruct::new("A".to_string());
println!("end main");
start main
new A
end main
drop A
match
のスコープ(if let
、while let
、for
も同様)
match
式でパターンマッチする変数は、match
式全体がスコープです。また、match
式のアーム内もそれぞれスコープになっています。
println!("start main");
match MyStruct::new("A".to_string()) { // new A
a => {
println!("start match");
let b = MyStruct::new("B".to_string()); // new B
println!("use {}", a.value());
println!("use {}", b.value());
println!("end match");
}, // drop B
} // drop A
println!("end main");
start main
new A
start match
new B
use A
use B
end match
drop B
drop A
end main
match
式でパターンマッチする変数のスコープがmatch
式全体ということは、束縛されない一時変数が破棄されるのもmatch
式の終了時点です。
println!("start main");
match MyStruct::new("A".to_string()).value() { // new A
a => {
println!("start match");
println!("use {}", a);
println!("end match");
},
} // drop A
println!("end main");
start main
new A
start match
use A
end match
drop A
end main
println!("start main");
let a = match MyStruct::new("A".to_string()).value() { // new A
a => {
println!("start match");
println!("use1 {}", a);
println!("end match");
a
},
// drop A
};
println!("use2 {}", a); // error MyStructのdrop後に、MyStruct::valueの参照を使用している!!
println!("end main");
error[E0716]: temporary value dropped while borrowed
--> src\main.rs:5:19
|
5 | let a = match MyStruct::new("A".to_string()).value() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
...
12 | };
| - temporary value is freed at the end of this statement
13 | println!("use2 {}", a);
| - borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
一時変数なので、ブロックで囲ってもブロック内が一つの式であれば生きたままです。
println!("start main");
match { MyStruct::new("A".to_string()).value() } { // new A
a => {
println!("start match");
println!("use {}", a);
println!("end match");
a
},
// drop A
};
println!("end main");
start main
new A
start match
use A
end match
drop A
end main
MyStruct
が、let a = MyStruct::new("A".to_string())
のように束縛された場合は、ブロックのスコープと同様の結果(コンパイルエラー)となります。
println!("start main");
match {
let a = MyStruct::new("A".to_string()); // new A
a.value()
// drop A
} {
a => {
println!("start match");
println!("use {}", a);
println!("end match");
a
},
};
println!("end main");
error[E0597]: `a` does not live long enough
--> src\main.rs:7:9
|
5 | match {
| ___________-
6 | | let a = MyStruct::new("A".to_string());
7 | | a.value()
| | ^^^^^^^^^ borrowed value does not live long enough
8 | | } { // new A
| | -
| | |
| |_____`a` dropped here while still borrowed
| borrow later used here
ちなみに余談ですが、この性質を利用して、macro_rules!
で式を受け取る際に、マクロの引数が一時的なスコープになっていても変数が破棄されないように、match
を使うテクニックがよく使われます。
下記の例はstd::assert_eq
のソースコードです。
macro_rules! assert_eq {
($left:expr, $right:expr $(,)?) => {
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = $crate::panicking::AssertKind::Eq;
// The reborrows below are intentional. Without them, the stack slot for the
// borrow is initialized even before the values are compared, leading to a
// noticeable slow down.
$crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::None);
}
}
}
};
($left:expr, $right:expr, $($arg:tt)+) => {
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = $crate::panicking::AssertKind::Eq;
// The reborrows below are intentional. Without them, the stack slot for the
// borrow is initialized even before the values are compared, leading to a
// noticeable slow down.
$crate::panicking::assert_failed(kind, &*left_val, &*right_val, $crate::option::Option::Some($crate::format_args!($($arg)+)));
}
}
}
};
}
今回はmatch
での検証のみ載せていますが、if let
、while let
、for
もmatch
と同じです。
構造体を作る式
クロージャのスコープ
クロージャは、クロージャ本体のスコープとクロージャ内部のスコープが存在します。
クロージャ内で作成した変数は、クロージャ内がスコープとなり、クロージャ外からクロージャににmoveした変数は、クロージャのスコープ=変数のスコープとなります。
下記の例では、"drop closure"、"drop B"、"end main"の順に出力されていることからわかります。
println!("start main");
let b = MyStruct::new("B".to_string()); // new B
{
println!("new closure");
let closure = move || {
println!("start closure");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
println!("use {}", b.value());
println!("end closure");
// drop A
};
closure();
println!("drop closure");
// drop B
}
println!("end main");
start main
new B
new closure
start closure
new A
use A
use B
end closure
drop A
drop closure
drop B
end main
moveせずに参照だけしている変数は、もとの変数のスコープのままとなります。
下記の例では、"drop closure"、"end main"、"drop B"の順に出力されていることからわかります。
println!("start main");
let b = MyStruct::new("B".to_string()); // new B
{
println!("new closure");
let closure = || {
println!("start closure");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
println!("use {}", b.value());
println!("end closure");
// drop A
};
closure();
println!("drop closure");
}
println!("end main");
// drop B
start main
new B
new closure
start closure
new A
use A
use B
end closure
drop A
drop closure
end main
drop B
async
ブロックのスコープ
async
ブロックもクロージャと同様、本体のスコープと内部のスコープが存在します。
しかし、Future
は基本的に'static
とFuture
自体の所有権を要求される場合が多いため、あまり意識する必要はないかもしれません。クロージャと同じく、moveした変数はFuture
と同じスコープとなります。
(Future
の検証のために、async-stdクレートを使用しています。)
println!("start main");
let c = MyStruct::new("C".to_string()); // new C
{
let future = async move {
println!("start future");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
async_std::task::yield_now().await;
let b = MyStruct::new("B".to_string()); // new B
println!("use {}", b.value());
async_std::task::yield_now().await;
println!("use {}", c.value());
println!("end future");
// drop B
// drop A
};
async_std::task::block_on(future);
// drop C
}
println!("end main");
start main
new C
start future
new A
use A
new B
use B
use C
end future
drop B
drop A
drop C
end main
下記のような、moveせずに参照のみ使うのは、あまり実用する機会はないですが、クロージャと同じで元のスコープと同じになります。
println!("start main");
let c = MyStruct::new("C".to_string()); // new C
{
let future = async {
println!("start future");
let a = MyStruct::new("A".to_string()); // new A
println!("use {}", a.value());
async_std::task::yield_now().await;
let b = MyStruct::new("B".to_string()); // new B
println!("use {}", b.value());
async_std::task::yield_now().await;
println!("use {}", c.value());
println!("end future");
// drop B
// drop A
};
async_std::task::block_on(future);
}
println!("end main");
// drop C
start main
new C
start future
new A
use A
new B
use B
use C
end future
drop B
drop A
end main
drop C
また、async
ブロック内で、await
をまたぐブロックも作成でき、内部にawait
を含むスコープを定義できます。
下記の例では、async
ブロック内のブロックの最後に"drop B"が出力されていることから、ブロックがスコープになっていることがわかります。
println!("start main");
let c = MyStruct::new("C".to_string()); // new C
{
let future = async move {
println!("start future");
let a = MyStruct::new("A".to_string()); // new A
{
println!("use {}", a.value());
async_std::task::yield_now().await;
let b = MyStruct::new("B".to_string()); // new B
println!("use {}", b.value());
async_std::task::yield_now().await;
println!("use {}", c.value());
// drop B
}
println!("end future");
// drop A
};
async_std::task::block_on(future);
// drop C
}
println!("end main");
start main
new C
start future
new A
use A
new B
use B
use C
drop B
end future
drop A
drop C
end main
これは一見普通に見えるかもしれません。しかし、Future
はFurure::poll
メソッドを通して実行されますが、一回の実行でawait
まで実行され、次回の実行は中断したawait
から再開されます。つまり、await
をまたぐ場合は、その前後は一連の処理ではなく、中断した時の状態はFuture
を実装した構造体に保存されています。なので、見た目はただのブロックのスコープですが、処理が分割されている特殊なスコープとなります。
また、async
ブロック内の変数の参照をawait
をまたいで保持することも可能です。
println!("start main");
{
let future = async move {
println!("start future");
for val in [
MyStruct::new("A".to_string()).value(),
MyStruct::new("B".to_string()).value(),
MyStruct::new("C".to_string()).value(),
] {
async_std::task::yield_now().await;
println!("use {}", val);
}
println!("end future");
};
async_std::task::block_on(future);
}
println!("end main");
start main
start future
new A
new B
new C
use A
use B
use C
drop C
drop B
drop A
end future
end main
これも一見普通に感じるかもしれませんが、先程の説明であったように、await
で一旦処理を中断するときに、await
をまたぐ参照も含めて中断した時の状態がFuture
を実装した構造体に保存されます。この参照は、Future
内の変数を参照しているため、自己参照となります。
通常、Rustでは自己参照は安全に定義できないのですが、Pin
によってmoveを禁止されている場合に限り自己参照は安全になります。Future
はFurure::poll
メソッドを通して実行されますが、poll
実行時は必ずFuture
をPin
でくるむ必要があります。そして、async
ブロックで定義したFuture
はUnpin
を実装していないため、一度Pin
でくるんだ後は、Pin
を解除することができず、永遠にPin
に包まれたままとなります。つまり、一度Pin
で包んだ後は、安全に自己参照を作ることができるのです。これが保証されているため、await
をまたぐ参照を保持することが可能となっています。
参考
Generator
のスコープ
Generator
(2022年7月現在、まだUnstableな機能)は、Future
とほぼ同じ仕組みとなっています。というより、Generator
を特化した定義がFuture
です。なので、スコープの性質もほぼ同じです。
#![feature(generators, generator_trait)]
println!("start main");
let c = MyStruct::new("C".to_string()); // new C
{
let mut generator = || {
println!("start generator");
let a = MyStruct::new("A".to_string()); // new A
{
println!("use {}", a.value());
yield;
let b = MyStruct::new("B".to_string()); // new B
println!("use {}", b.value());
yield;
println!("use {}", c.value());
// drop B
}
println!("end generator");
// drop A
};
loop {
match std::pin::Pin::new(&mut generator).resume(()) {
std::ops::GeneratorState::Yielded(()) => {},
std::ops::GeneratorState::Complete(()) => break,
}
}
}
println!("end main");
// drop C
start main
new C
start generator
new A
use A
new B
use B
use C
drop B
end generator
drop A
end main
drop C
しかし、このGenerator
の構文で作成したGenerator
は、デフォルトでUnpin
を実装しています。そのため、自己参照、つまり、yield
をまたぐ参照を保持することが禁止されています。
#![feature(generators, generator_trait)]
println!("start main");
{
let mut generator = || {
println!("start future");
for val in [
MyStruct::new("A".to_string()).value(),
MyStruct::new("B".to_string()).value(),
MyStruct::new("C".to_string()).value(),
] {
yield;
println!("use {}", val);
}
println!("end future");
};
loop {
match std::pin::Pin::new(&mut generator).resume(()) {
std::ops::GeneratorState::Yielded(()) => {},
std::ops::GeneratorState::Complete(()) => break,
}
}
}
println!("end main");
error[E0626]: borrow may still be in use when generator yields
--> src\main.rs:10:17
|
10 | MyStruct::new("A".to_string()).value(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
14 | yield;
| ----- possible yield occurs here
デフォルトでは禁止されていますが、Generator
もmoveを禁止すれば(=Unpin
を実装しなければ)、安全に自己参照することも可能です。そして、Unpin
を実装しないパターンのGenerator
を定義する構文も用意されています。
Generator
の定義前(move
等を記述する場所)にstatic
と付け加えると、Unpin
を実装しないGenerator
を作成できます。
#![feature(generators, generator_trait)]
println!("start main");
{
let generator = static || {
println!("start future");
for val in [
MyStruct::new("A".to_string()).value(),
MyStruct::new("B".to_string()).value(),
MyStruct::new("C".to_string()).value(),
] {
yield;
println!("use {}", val);
}
println!("end future");
};
// !UnpinのGeneratorはPin::newに渡せないため、Box::pinを作成する
let mut generator = Box::pin(generator);
loop {
match generator.as_mut().resume(()) {
std::ops::GeneratorState::Yielded(()) => {},
std::ops::GeneratorState::Complete(()) => break,
}
}
}
println!("end main");
start main
start future
new A
new B
new C
use A
use B
use C
drop C
drop B
drop A
end future
end main
これでUnpin
と引き換えに自己参照が可能となり、通常のスコープと同じ感覚で使用することができます。ちなみに、Generator
のresume
メソッドにわたす引数、戻り値のライフタイムは、Generator
よりも長い必要があるため、Generator
の開始~終了まで保持することができます。逆を言うと、Generator
よりも短いライフタイムの参照は渡す・受け取ることができません。
Generator
はまだ不安定な機能であり、仕様が今後変更される可能性があります。(私的には、このままの仕様で安定化は無いと思っています。)
余談
本筋で書きたかったことはここまでです。残りは、この記事を読んで気になりそうなことに少し触れたいと思います。
Drop
トレイトの影響
ここまで話してきた変数のスコープは、実際にはもう少し短くなることが多いです。Rustのコンパイラは構文を解析して、変数が必要無くなった時点で破棄された扱いとなります。
この動作はrust2018からの仕様で、"non-lexical lifetime"と呼ばれています。この仕様に関しては
の記事に詳しく説明されており、おすすめです。しかし、今までの例で示した結果では、変数が不要になったあともスコープの限界まで生存しています。これは、Drop
処理を実行する必要があるため、もう少し正確に言うと、自身か自身のメンバがDrop
トレイトを実装している場合に、生存期間がスコープの限界まで伸びます。
例えば、下記のコードは、一見&a
が残ってるうちにa
を書き換えているため、エラーとなるように見えますが、MyStruct
がDrop
を実装していない場合は、a
を書き換える前にa_ref
が不要になるため、コンパイルできます。Drop
を実装している場合は、a
を書き換えたあとにa_ref
のdrop
処理が必要になるため、&a
が残っている間にa
を書き換えることになり、コンパイルエラーとなります。
let mut a = MyStruct::new("A".to_string());
{
let a_ref = MyStruct::new(&a);
println!("use {}", a_ref.value());
a = MyStruct::new("B".to_string());
}
error[E0506]: cannot assign to `a` because it is borrowed
--> src\main.rs:8:8
|
7 | let a_ref = MyStruct::new(&a);
| -- borrow of `a` occurs here
8 | a = MyStruct::new("B".to_string());
| ^ assignment to borrowed `a` occurs here
9 | }
| - borrow might be used here, when `a_ref` is dropped and runs the `Drop` code for type `MyStruct`
この辺のもう少し厳密なケースについて、
に載っているので気になる方がご参照ください。このように、既存の構造体にDrop
トレイトを実装するのは、それだけで破壊的変更になるので、注意しましょう。
mutableな変数の書き換え
変数は、スコープによる破棄以外に、書き換えられることによっても破棄されます。Rustのコンパイラはこの破棄による生存チェックもある程度行ってくれます。
例として、以下のコードはコンパイルが通ります。
let mut a = MyStruct::new("a".to_string());
let mut a_ref = &a;
for i in 1..3 {
a = MyStruct::new("a".to_string() + &i.to_string());
a_ref = &a;
println!("use {}", a_ref.value());
a = MyStruct::new("x".to_string());
}
ループ中でa_ref
を使うタイミングでa
が生存していることをコンパイラが判断できています。これを以下のようにa
書き換え後にa_ref
にアクセスするとしっかりエラーとなります。
let mut a = MyStruct::new("a".to_string());
let mut a_ref = &a;
for i in 1..3 {
a = MyStruct::new("a".to_string() + &i.to_string());
a_ref = &a;
println!("use {}", a_ref.value());
a = MyStruct::new("x".to_string());
}
println!("use {}", a_ref.value());
error[E0506]: cannot assign to `a` because it is borrowed
--> src\main.rs:11:9
|
9 | a_ref = &a;
| -- borrow of `a` occurs here
10 | println!("use {}", a_ref.value());
11 | a = MyStruct::new("x".to_string());
| ^ assignment to borrowed `a` occurs here
12 | }
13 | println!("use {}", a_ref.value());
| ------------- borrow later used here
このチェックは、構文を解析した結果であり、具体的な値までチェックはしていないため、下記のコードはa_ref
は常に有効ですが、エラーとなります。
let mut a = MyStruct::new("a".to_string());
let mut a_ref = &a;
for i in 1..3 {
a = MyStruct::new("a".to_string() + &i.to_string());
a_ref = &a;
println!("use {}", a_ref.value());
if false {
a = MyStruct::new("x".to_string());
}
}
println!("use {}", a_ref.value());
error[E0506]: cannot assign to `a` because it is borrowed
--> src\main.rs:12:13
|
9 | a_ref = &a;
| -- borrow of `a` occurs here
...
12 | a = MyStruct::new("x".to_string());
| ^ assignment to borrowed `a` occurs here
...
15 | println!("use {}", a_ref.value());
| ------------- borrow later used here
ただし、Rustはこの辺の処理の改善に結構力を入れているため、将来変更される可能性があります。
一時変数のライフタイム延長
この記事で使用しているMyStruct
には、わざわざfn value(&self) -> &T
プロパティを作っているですが、これを使わずにフィールドに直接let value = &MyStruct::new("a".to_string()).value
のようにアクセスすると、コンパイラが気を利かせてself
のライフタイムをvalue
の生存中は生きているように延長してくれます。
下記のコードは、以前のチャプターでエラーとなったコードです。
println!("start main");
let a: &String = MyStruct::new("A".to_string()).value(); // new A drop A
println!("use {}", a); // error MyStructのdrop後に、MyStruct::valueの参照を使用している!!
println!("end main");
このコードをフィールドに直接アクセスするよう変更すると、MyStruct
の生存期間が伸び、コンパイルは通るようになります。
println!("start main");
let a: &String = &MyStruct::new("A".to_string()).value; // new A
println!("use {}", a);
println!("end main");
// drop A
start main
new A
use A
end main
drop A
こう見ると不思議な気持ちになりますが、実際にプログラミングしているときに注意して見てみると、このような生存期間の延長によく助けられていることに気が付くと思います。例えばlet x = &1;
というコードも、生存期間の延長によって成り立っています。
もう少し詳しく知りたい方は、
にそこそこ詳しく載っていますのでご参照ください。
Discussion