Rustのエラー E0515: cannot return reference to local variable(ローカル変数への参照)
E0515 は、関数が自分で作ったものへの参照(&)を返そうとしている、という意味です。関数の中で作ったものは、関数が戻るときにすべて片付けられます。そのため、その参照は何もない場所を指すことになってしまいます。Rust の初心者はほぼ全員がこのエラーに出会いますが、たいていは & を1つ消して、値そのものを返せば解決します。(このページは Rustのコンパイルエラー解説 の一部です。)
エラーメッセージ
Section titled “エラーメッセージ”error[E0515]: cannot return reference to local variable `greeting` --> src/main.rs:3:5 |3 | &greeting // ❌ 関数から戻るときに greeting は破棄される | ^^^^^^^^^ returns a reference to data owned by the current function「ローカル変数 greeting への参照は返せません。現在の関数が所有しているデータへの参照を返しています」という意味です。
このエラーが出るプログラムはこちらです。
fn make_greeting(name: &str) -> &str { let greeting = format!("Hello, {name}!"); // 新しい String。この関数が所有している &greeting // ❌ 関数から戻るときに greeting は破棄される}
fn main() { let text = make_greeting("Ferris"); println!("{text}");}同じエラーでも、言い回しがいくつかあります。意味はどれも同じです。
cannot return reference to local variable:&greetingと書いたとき。cannot return value referencing local variable:greeting.as_str()や&scores[2..]のように、借用するメソッドなどを呼んだとき。cannot return value referencing temporary value:text.to_uppercase().as_str()のように、データが一度も変数に入っていない(一時的な値だった)とき。
どの場合も、“returns a value referencing data owned by the current function”(現在の関数が所有するデータを参照する値を返している)というラベルが付きます。
どういう意味?
Section titled “どういう意味?”greeting はローカル変数です。make_greeting の中で作られ、この関数が所有しています。関数が戻ると、ローカル変数はすべて破棄(drop)されます。返そうとしている &str は、もう存在しない String の中を指すことになってしまうのです。
アパートを引き払ったあとで、友だちに部屋の鍵を渡すようなものです。鍵は本物ですが、部屋はもう空っぽか、別の人が住んでいます。友だちに必要なのは鍵ではなく、家具のほうです。
よくある流れはこうです。まず &str を返す関数で greeting(String)をそのまま返して E0308(mismatched types)が出ます。するとコンパイラが「consider borrowing here: &greeting」(ここで借用してはどうか)と提案してきます。この提案で型の不一致は直りますが、そのまま E0515 にたどり着いてしまいます。本当に直すべきなのは、戻り値の型のほうです。
Rust がこうする理由
Section titled “Rust がこうする理由”参照は、指している値より長く生きてはいけません。ローカル変数への参照を返すと、ダングリング参照(dangling reference)、つまり解放済みのメモリを指すポインタができてしまいます。C や C++ ではこれがコンパイルできてしまい、あとでクラッシュします(もっと悪いと、黙ってでたらめな値を返します)。Rust はこれをコンパイル時に見つけてくれます。
返される参照は、関数の外で生きている何かから借りたものでなければなりません。呼び出し側が渡した引数、self のフィールド、文字列リテラルのような 'static な値などです。関数自身のローカル変数は、この条件を満たしません。だからこそ、ライフタイム注釈では E0515 は直せません。'a はデータがどれだけ生きるかを説明するだけで、ローカル変数を長生きさせることはできないのです。所有権と借用のページもあわせて読んでみてください。
直し方1:所有権のある値を返す
Section titled “直し方1:所有権のある値を返す”関数がデータを作るときはこれを選びましょう。ほとんどの場合、これが正解です。&str の代わりに String を(&[T] の代わりに Vec<T> を)返し、& を消します。値は呼び出し側にムーブされ、呼び出し側がそれを所有するようになります。
fn make_greeting(name: &str) -> String { let greeting = format!("こんにちは、{name}さん!"); greeting // ✅ String をまるごと呼び出し側に渡す}
fn top_scores() -> Vec<u32> { let mut scores = vec![42, 97, 63, 88]; scores.sort(); scores.split_off(2) // 上位2つを、新しい Vec として}
fn main() { let text = make_greeting("Ferris"); println!("{text}"); println!("{:?}", top_scores());}実行結果:
こんにちは、Ferrisさん![88, 97]コストの心配はいりません。String や Vec を返すときにムーブされるのは、ポインタ・長さ・容量という小さなデータだけです。文字列の中身がコピーされるわけではありません。最後の行に format!("こんにちは、{name}さん!") とだけ書いても同じです。
直し方2:データを引数で受け取り、その中への参照を返す
Section titled “直し方2:データを引数で受け取り、その中への参照を返す”呼び出し側がすでにデータを所有していて、その一部を指したいだけのときはこれを選びましょう。関数が借用した引数を使って処理するなら、返す参照はその引数から借りたものになります。引数は関数の外で生きているので安全です。
fn top_two(scores: &mut [u32]) -> &[u32] { scores.sort(); &scores[scores.len() - 2..] // ✅ 呼び出し側のデータの中を指す}
fn main() { let mut scores = vec![42, 97, 63, 88]; // main が所有している let best = top_two(&mut scores); println!("{best:?}");}実行結果:
[88, 97]参照の引数が1つなら、ライフタイムは Rust が補ってくれます。2つ以上ある場合は 'a が必要になることがあります(E0106 を参照)。この方法が使えるのは、入力の一部を返すときだけです。入力をもとに作った新しい値(to_uppercase()、format!、trim().to_string() など)は、やはりローカル変数なので、直し方1に戻りましょう。スライスのページも参考になります。
直し方3:文字列リテラルなら &'static str を返す
Section titled “直し方3:文字列リテラルなら &'static str を返す”返す可能性のある結果が、すべてコードに書いた決まった文字列のときはこれを選びましょう。文字列リテラルはプログラム本体に保存されていて、ずっと生きているので、その参照を返すのはいつでも安全です。
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => "Unknown", }}
fn main() { for code in [200, 404, 418] { println!("{code}: {}", status_text(code)); }}実行結果:
200: OK404: Not Found418: Unknownformat! の結果をシグネチャに &'static str と書いて「直そう」とはしないでください。String はローカル変数のままなので、また E0515 が出るだけです。
直し方4:借用するときと作るときがあるなら Cow を使う
Section titled “直し方4:借用するときと作るときがあるなら Cow を使う”ふだんは入力をそのまま返すけれど、ときどき新しい文字列を作らないといけない、という関数ならこれを選びましょう。Cow(clone-on-write)は、借用した &str か所有権のある String のどちらかを持てるので、必要なときだけメモリを確保できます。
use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> { if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) // 必要なときだけ新しい String を作る } else { Cow::Borrowed(name) // コピーはまったくしない }}
fn main() { for name in ["report.txt", "my notes.txt"] { let clean = safe_file_name(name); let kind = match clean { Cow::Borrowed(_) => "借用", Cow::Owned(_) => "所有", }; println!("{clean}({kind})"); }}実行結果:
report.txt(借用)my_notes.txt(所有)Cow は最適化のための道具です。必要かどうか迷ったら、String を返しましょう(直し方1)。詳しくは Weak と Cow のページで説明しています。
よく見かける場面
Section titled “よく見かける場面”ローカルな String の s.as_str() を返す。 greeting.as_str() は greeting を借用するので、&greeting と同じように失敗します。言い回しは “cannot return value referencing local variable” になります。直し方:greeting を返し、戻り値の型を String にします。
新しい文字列を作る .map()。 一見問題なさそうですが、to_lowercase() はそのたびに一時的な String を作り、それは .as_str() のすぐあとで破棄されてしまいます。
fn tags(input: &str) -> Vec<&str> { input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect() // ❌ cannot return value referencing temporary value}直し方:Vec<String> を返し、.as_str() を消します。.map(|t| t.trim().to_lowercase()).collect() です。(to_lowercase() がなければ、t.trim() は input のスライスなので、Vec<&str> のままでも動きます。イテレータのページも参考にしてください。)
フィールドを整形して返すメソッド。 fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) } は “cannot return reference to temporary value” になります。直し方:-> String にします。&self.first を返すのは問題ありません。そのデータはメソッドの中ではなく、構造体の中にあるからです。構造体のページも参考にしてください。
参照の引数がない関数。 fn make_greeting() -> &String は、それより前の段階で E0106 になります。借りてくる元が何もないからです。直し方は同じで、String を返します。
関連するエラー
Section titled “関連するエラー”- E0106: missing lifetime specifier:先にこちらが出ることがよくあります。ただし、
'aを付けてもローカル変数への参照は直りません。 - E0597: borrowed value does not live long enough:同じ問題が1つの関数の中で起きたものです。戻り値の代わりに、ブロックやループが原因になります。
- E0308: mismatched types:
&strと宣言したところでStringを返したときに出ます。&を付けるのではなく、戻り値の型を変えましょう。 - E0382: borrow of moved value:所有権のある値に切り替えたあとは、ムーブした値をまた使ってしまわないように気をつけましょう。
- E0716: temporary value dropped while borrowed:一時的な値への参照を、同じ関数の中であとから使おうとしたときに出ます。
- コンパイルエラー解説の一覧
理解度チェック
Section titled “理解度チェック”理解度チェック
1. format! でメッセージを作り、&message を返す関数があります。たいていの場合、正しい直し方はどれでしょう?
2. コンパイルできる関数はどれでしょう?
3. 戻り値の型として Cow<str> が向いているのはどんなときでしょう?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0515 — Rust error codes index(英語):このエラーの公式の説明です。
- 宙に浮いた参照 — The Rust Programming Language 日本語版:
dangleの例と、なぜStringをそのまま返すのが正解なのかが説明されています。 - ライフタイムで参照を検証する — The Rust Programming Language 日本語版:返す参照が引数のどれかと結びついていなければならない理由がわかります。
- スライス型 — The Rust Programming Language 日本語版:入力から借りた
&strスライスを返す方法で、直し方2の安全なやり方です。 Cow— standard library docs(英語):直し方4で使った clone-on-write 型の説明と例です。