コンテンツにスキップ

Rustのエラー E0597: borrowed value does not live long enough(値の寿命が足りない)

E0597 は、参照(&x)を、値 x そのものより長く持ち続けようとした、という意味のエラーです。値はそのブロックの終わりで片付けられてしまうのに、ブロックの外の何かがまだその参照を使おうとしているのです。とてもよく出るエラーで、特に内側のブロック、ループ、構造体でよく見かけます。直し方はたいてい、1行を移動するか、参照ではなく所有する値を持つようにするだけです。(このページは Rustのコンパイルエラー解説の一部です。)

error[E0597]: `x` does not live long enough
--> src/main.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x; // r が x を借用する
| ^^ borrowed value does not live long enough
6 | } // x はここで破棄される
| - `x` dropped here while still borrowed
7 | println!("r: {r}"); // ❌ r は何もない場所を指すことになる
| - borrow later used here

「x の生存期間が足りない」という意味です。ラベルはそれぞれ「x はここで宣言された」「借用された値の生存期間が足りない」「x は借用されたままここで破棄される」「借用はこのあとここで使われる」と言っています。

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

fn main() {
let r;
{
let x = 5;
r = &x; // r が x を借用する
} // x はここで破棄される
println!("r: {r}"); // ❌ r は何もない場所を指すことになる
}

4つのラベルが、順番に何が起きたかを物語っています。

  1. 4行目: x が内側のブロック { ... } の中で作られます。
  2. 5行目: r が x を借用します。ここまでは問題ありません。
  3. 6行目: ブロックが終わるので、x が破棄(drop)されます。でも借用はまだ生きています。
  4. 7行目: r が使われますが、指している値はもうなくなっています。

友だちが泊まっているホテルの部屋番号をメモしたところを想像してください。友だちが泊まっている間は役に立ちます。でもチェックアウトしたあとは、メモの番号はそのまま残っていても、行き先は空き部屋か、知らない人の部屋です。Rust は、空き部屋につながるメモを持ち続けることを許してくれないのです。

「borrowed value does not live long enough」(借用された値の生存期間が足りない)という短い文言を見ることもあります。同じエラーで、見出しのほうに変数名が入っているかどうかの違いだけです。

参照は、指している値より長生きしてはいけません。もし長生きしたら、それはダングリング参照(dangling reference、宙に浮いた参照)です。解放済みで、今は別の何かが入っているかもしれないメモリを指すポインタのことです。C や C++ では、これがクラッシュやセキュリティバグの典型的な原因になっています(「use after free」、解放後の使用)。

Rust のすべての値は、所有者がスコープを抜けたときに破棄されます。たいていは、宣言されたブロックの閉じ } です。借用チェッカーは2つの範囲を比べます。値がどれだけ生きるかと、参照がどれだけ使われるかです。値の } よりあとで参照が使われると、E0597 になります。スコープ、借用、ライフタイムも見てください。

コンパイラが文句を言っていないところにも注目してください。5行目で x を借用すること自体は問題ないのです。問題なのは、7行目のあとでの使用だけです。最後の行を消せば、プログラムはコンパイルできます。これが、このあとの直し方のヒントになります。値をもっと長生きさせるか、参照を使い終えるのを早めるか、のどちらかです。

直し方1:値を外側のスコープへ移す

Section titled “直し方1:値を外側のスコープへ移す”

参照が必要な間ずっと、値も本当に必要なときに使います。これがいちばんよくある直し方です。値をブロックの前に(外側で)宣言して、少なくとも参照と同じだけ生きるようにします。

fn main() {
let scores = vec![72, 95, 88]; // これで main の終わりまで生きる
let best;
{
best = scores.iter().max().unwrap();
}
println!("最高得点: {best}"); // ✅ scores はまだ生きている
}

実行結果:

最高得点: 95
▶ 実際に試してみよう

目安はこうです。所有者は借りる側より先に宣言して、借りる側より長生きさせること。 ブロックやループの中で作った値への参照を外で使っていたら、値を外に出しましょう。

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

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

ループの中で作った値を集めたいときに使います。ループは1周ごとに新しい String を作り、その周の終わりに破棄します。だから、ループの外にある Vec<&str> は、それらへの参照を持ち続けられません。

fn main() {
let mut names: Vec<&str> = Vec::new();
for i in 1..=3 {
let name = format!("player{i}");
names.push(&name); // ❌ `name` does not live long enough
}
println!("{names:?}");
}

直し方は、Vec に文字列を所有させることです。Vec<&str> を Vec<String> に変えて、String そのものを push します(これで値がベクタの中にムーブされるので、ベクタと同じだけ生きます)。

fn main() {
let mut names: Vec<String> = Vec::new();
for i in 1..=3 {
let name = format!("player{i}");
names.push(name); // String を Vec の中にムーブする
}
println!("{names:?}");
}

実行結果:

["player1", "player2", "player3"]
▶ 実際に試してみよう

構造体や HashMap でも考え方は同じです。データがコンテナより狭いスコープで作られるなら、&str(や &[T])ではなく String(や Vec<T>)を持ちましょう。初心者のうちは、所有するフィールドにしておけば、ほとんどの場合うまくいきます。文字列とベクタも見てください。

元の値もそのまま使い続けたいときや、そもそも借用しか持っていないときに使います。参照を持つ構造体(Label<'a> { text: &'a str })は、指している文字列より長生きできません。構造体に所有する String のフィールドを持たせて、コピーを入れましょう。

struct Label {
text: String, // &str ではなく、所有する String
}
fn main() {
let label;
{
let title = String::from("Inbox");
label = Label { text: title.clone() };
println!("ブロックの中: {title}");
}
println!("ラベル: {}", label.text); // ✅ label は自分用のコピーを持っている
}

実行結果:

ブロックの中: Inbox
ラベル: Inbox
▶ 実際に試してみよう

あとで title を使わないなら、clone せずにムーブすれば十分です。Label { text: title } と書きます。短い文字列の clone は安上がりなので、まったく気にしなくて大丈夫です。&str しか持っていないなら、.to_string() で所有するコピーを作れます。

直し方4:借用が間に合ううちに終わるよう組み立て直す

Section titled “直し方4:借用が間に合ううちに終わるよう組み立て直す”

値がもともと短い間だけ使うものであるときに使います。値を長生きさせるのではなく、値がまだ生きているうちに参照を使い切るのです。参照を返す関数でよく出てきます。おなじみの longest の例を見てみましょう。

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("Ferris the crab");
{
let second = String::from("Corro");
let winner = longest(&first, &second);
println!("いちばん長い名前: {winner}"); // ✅ second が破棄される前に使う
}
}

実行結果:

いちばん長い名前: Ferris the crab
▶ 実際に試してみよう

(longest は .len() でバイト数を比べるので、名前は英語のままにしています。日本語の文字は1文字3バイトになるため、比べた結果が変わってしまうことがあるからです。)

winner をブロックの外で宣言して、ブロックのあとで表示すると、second について E0597 が出ます。たまたま first のほうが長くても、です。シグネチャが「結果はどちらの引数から借用しているかもしれない」と言っているので、両方とも結果より長生きしなければならないのです。どうしても結果をあとで使いたいなら、所有する値にしておきましょう。let winner = longest(&first, &second).to_string(); と書きます。

ブロックの外に置いたクロージャ。 name を使うクロージャは、name を借用します。クロージャが name より長生きすると E0597 になり、value captured here(値はここでキャプチャされた)というラベルが追加されます。

let say_hello;
{
let name = String::from("Ferris");
say_hello = || println!("Hello, {name}!"); // ❌ `name` does not live long enough
}
say_hello();

直し方:move || ... と書いて、クロージャに name の所有権を持たせます。クロージャも見てください。

スレッド。 thread::spawn(|| ...) の中でローカル変数を借用すると、別のエラー E0373(「closure may outlive the current function」、クロージャが現在の関数より長生きするかもしれない)になります。スレッドは main より長く動くかもしれないからです。直し方:thread::spawn(move || ...) にするか、Arc で共有するか、std::thread::scope を使います。並行処理も見てください。

一時的な値への参照。 let label = Label { text: String::from("Inbox").as_str() }; は E0597 によく似ていますが、E0716(「temporary value dropped while borrowed」)として表示されます。この String は一度も変数に入らないので、文の終わりで破棄されるのです。直し方:先に変数に入れる(let title = String::from("Inbox");)か、フィールドを String にします。

関数から参照を返すとき。 値が関数の中で作られている場合は、代わりに E0515 が出ます。所有する値を返しましょう。

理解度チェック

1. let r; { let x = 5; r = &x; } のあとで r を println! しています。E0597 の本当の引き金はどれですか?

2. ループの1周ごとに String を作り、&name を Vec<&str> に push しています。ふつうはどう直しますか?

3. スレッドのクロージャがローカルの Vec を借用しています。どのエラーが出ますか?

スコア: 0 / 3