コンテンツにスキップ

Rustのエラー E0716: temporary value dropped while borrowed(借用中に一時的な値が破棄)

E0716 は、変数名のない値から借用して、その借用をあとの行でも使い続けようとした、という意味のエラーです。Rust はそういう名前のない値を文の終わりで捨ててしまうので、借用は何もない場所を指すことになります。見た目はこわそうですが、借用のエラーの中でもいちばん直しやすいもののひとつです。たいていは、1行を2行に分けるだけで解決します。(このページは Rustのコンパイルエラー解説の一部です。)

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:13
|
2 | let s = String::from("hi").as_str(); // s は名前のない String を借用している
| ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("{s}"); // ❌ その String はもうない
| - borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
2 ~ let binding = String::from("hi");
3 ~ let s = binding.as_str(); // s は名前のない String を借用している
|

「借用されている間に、一時的な値が破棄された」という意味です。ラベルは「ここで一時的な値が作られ、まだ使われているのに解放される」「一時的な値はこの文の終わりで解放される」「借用はこのあとここで使われる」と言っています。help は「let で束縛して、もっと長生きする値を作ってはどうか」と提案しています。

このエラーが出るのは、次のプログラムです。

fn main() {
let s = String::from("hi").as_str(); // s は名前のない String を借用している
println!("{s}"); // ❌ その String はもうない
}

一時的な値(temporary)とは、式の途中で作られたけれど、一度も変数に入れられなかった値のことです。上の行では、String::from("hi") が本物の String を作りますが、それを所有している人がいません。let 名前 = ... がないのです。Rust は一時的な値を、文の終わり、つまり ; のところで破棄(drop)します。

3つのラベルを左から右へ読んでいきましょう。

  1. String::from("hi") の下の ^^^^: ここで一時的な String が作られます。
  2. 行の終わりの -: ここ、つまり ; のところで、その String が解放されます。
  3. 3行目の s の下の -: ところが s(その String の中を指す &str)は、ここでまだ使われています。

注文を付せんにメモして、席を離れたとたんにその付せんを捨ててしまう店員さんを想像してみてください。友だちに「注文はあの付せんに書いてあるよ」と言っても、友だちは何も見つけられません。メモは自分で取っておくか、ちゃんと残る場所に書き写す必要があります。

エラーメッセージの help: の部分が、たいていそのまま答えになります。変数を作って(rustc はそれを binding と呼んでいます)、値にもっと長生きする所有者を与えるのです。

これは E0597 と同じルールです。参照は、指している値より長生きしてはいけません。 そうでないと、解放済みのメモリを指すダングリング参照になってしまいます。違いはひとつだけです。E0597 では値に名前があって } で破棄されますが、E0716 では値に名前がなく、; で破棄されます。

一時的な値を同じ文の中で使うのは、まったく問題ありません。println!("{}", String::from("hi").as_str()); はコンパイルできます。借用が ; より前に終わるからです。エラーになるのは、借用があとの行でもまだ必要なときだけです。借用はいつ終わる?とライフタイムも見てください。

ひとつ、助かる例外があります。一時的な値のライフタイム延長(temporary lifetime extension)と呼ばれるものです。let の中で、一時的な値のすぐ前に &(または &mut)を付けると、Rust はその一時的な値を、変数と同じだけこっそり生かしておいてくれます。だから、次の行はコンパイルできます。

struct Label<'a> {
text: &'a str,
}
fn make_title() -> String {
String::from("Inbox")
}
fn main() {
let greeting = &String::from("hi"); // ✅ 延長される:let の中ですぐ前に &
let queue = &mut vec![1, 2, 3]; // ✅ 延長される
queue.push(4);
let label = Label { text: &make_title() }; // ✅ 延長される:構造体リテラルの中ですぐ前に &
println!("{greeting} {queue:?} {}", label.text);
}

実行結果:

hi [1, 2, 3, 4] Inbox
▶ 実際に試してみよう

この延長は、メソッド呼び出しや関数呼び出しを通り抜けません。String::from("hi").as_str()、make_title().as_str()、vec![1, 2].iter()、shout(&make_title()) は、どれも呼び出しの中で一時的な値を借用しています。そのため一時的な値はやはり ; で破棄され、E0716 になります。全部のケースを覚えようとしなくて大丈夫です。E0716 が出たら、値に名前を付けましょう。

直し方1:let で一時的な値に名前を付ける

Section titled “直し方1:let で一時的な値に名前を付ける”

ほとんどの場合はこれを選びます。コンパイラが提案しているのもこの方法で、E0716 の大半はこれで直ります。まず値を専用の変数に入れて、次の行でその変数から借用します。

とてもよくあるのが、入力した1行を分割する場面です。read_command() は新しい String を返し、.trim() と .split(' ') がそこから借用し、Vec<&str> がその借用を持ち続けます。

let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716

Rust は read_command() を指して、文の終わりで解放されると言います。名前を付けましょう。

fn read_command() -> String {
// 実際のプログラムでは、ここで stdin から1行読み込みます。
String::from(" add milk eggs \n")
}
fn main() {
let line = read_command(); // これで String は main の終わりまで生きる
let parts: Vec<&str> = line.trim().split(' ').collect();
println!("{parts:?}");
println!("コマンド: {}、品目数: {}", parts[0], parts.len() - 1);
}

実行結果:

["add", "milk", "eggs"]
コマンド: add、品目数: 2
▶ 実際に試してみよう

本物の入力でも、まったく同じ目にあいます。io::stdin().lines() に対して lines.next().unwrap().unwrap().trim().split(' ').collect() と書くと、同じ理由で E0716 になります。先に let line = lines.next().unwrap().unwrap(); を1行で書いておきましょう。文字列も見てください。

直し方2:参照ではなく所有する値を持つ

Section titled “直し方2:参照ではなく所有する値を持つ”

借用が、作れるどの変数よりも長く生きる必要があるときに使います。たとえば、あちこちに渡す構造体や、ループの中で要素を詰めていく Vec です。ループの中の let は1周ごとに破棄されるので、E0716 が E0597 に変わるだけです。

うまくいかない例を2つ見てみましょう。一時的な値から取った &str を持つ構造体と、新しい文字列の借用を push するループです。

let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase().as_str()); // ❌ E0716
}

直し方は、構造体と Vec に文字列を所有させることです。&str の代わりに String を使い、.as_str() を取ります。

struct Label {
text: String, // 所有するので、ライフタイムは要らない
}
fn make_title() -> String {
String::from("Inbox")
}
fn main() {
let label = Label { text: make_title() }; // String が Label の中にムーブされる
println!("ラベル: {}", label.text);
let mut names: Vec<String> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase()); // 新しい String はそれぞれ Vec が所有する
}
println!("{names:?}");
}

実行結果:

ラベル: Inbox
["FERRIS", "CORRO"]
▶ 実際に試してみよう

初心者のうちは、所有するフィールド(String、Vec<T>)にしておくのが、ほとんどの場合いちばんかんたんです。&str のフィールドを持つ構造体にはライフタイムが必要で、借りているデータより長生きすることはできません。参照を持つ構造体と構造体も見てください。

直し方3:チェーンを組み立て直す

Section titled “直し方3:チェーンを組み立て直す”

一時的な値が、イテレータのチェーンの出発点になっているときに使います。次の行は、vec![...] で Vec を作り、.iter() で借用して、そのイテレータ(またはその結果)をあとのために取っておこうとしています。

let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716
let best = vec![72, 95, 88].iter().max(); // ❌ E0716

前に付いている &mut は役に立ちません。ライフタイム延長で生き残るのはイテレータだけで、.iter() が借用している Vec は生き残らないからです。抜け道は2つあります。

  • into_iter() を使って、イテレータに Vec を所有させる(rustc もこれを提案します)。そうすれば &mut も要らず、let mut だけで済みます。
  • .copied()、.cloned()、.sum()、数値の .collect::<Vec<_>>() などで、チェーンを所有する結果で終える。そうすれば、; のあとで一時的な値を借用しているものは何もなくなります。
fn main() {
// イテレータが Vec を所有するので、一時的な値を借用するものはない
let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10);
println!("{:?}", doubled.next());
println!("{:?}", doubled.next());
// .copied() で Option<&i32> を、借用のない値 Option<i32> に変える
let best = vec![72, 95, 88].iter().max().copied();
println!("最高: {best:?}");
}

実行結果:

Some(10)
Some(20)
最高: Some(95)
▶ 実際に試してみよう

あとでその Vec をもう一度使いたいなら、直し方1に戻りましょう。let scores = vec![72, 95, 88]; と書いてから、scores.iter().max() とします。イテレータも見てください。

チェーンの途中で Mutex をロックするとき。 let first = names.lock().unwrap().first(); は E0716 になります。ロックのガードが一時的な値だからです。ガードが破棄されるとロックが解除されるので、first が中を指し続けることはできません。直し方:let guard = names.lock().unwrap(); と書いてから guard.first() とするか、.first().cloned() で一度にデータをコピーして取り出します。共有状態も見てください。

参照を返す関数に &一時的な値 を渡すとき。 fn shout(s: &str) -> &str があるとき、let t = shout(&make_title()); は E0716 になります。返ってくる &str が一時的な String を借用していて、ライフタイム延長は関数呼び出しの先までは届かないからです。直し方:let title = make_title(); と書いてから shout(&title) とします。

関数からそれを返すとき。 参照を返す関数(-> &'static str など)の最後の行に make_title().as_str() と書くと、代わりに E0515(「cannot return value referencing temporary value」、一時的な値を参照する値は返せない)が出ます。直し方:String を返しましょう。

理解度チェック

1. Rust でいう「一時的な値」(temporary)とは何ですか?

2. let s = String::from("hi").as_str(); のあとに println!("{s}"); と書くと E0716 になります。ふつうはどう直しますか?

3. 一時的な値のライフタイム延長のおかげでコンパイルできるのは、どの行ですか?

スコア: 0 / 3