コンテンツにスキップ

Rustのエラー E0515: cannot return reference to local variable(ローカル変数への参照)

E0515 は、関数が自分で作ったものへの参照(&)を返そうとしている、という意味です。関数の中で作ったものは、関数が戻るときにすべて片付けられます。そのため、その参照は何もない場所を指すことになってしまいます。Rust の初心者はほぼ全員がこのエラーに出会いますが、たいていは & を1つ消して、値そのものを返せば解決します。(このページは Rustのコンパイルエラー解説 の一部です。)

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”(現在の関数が所有するデータを参照する値を返している)というラベルが付きます。

greeting はローカル変数です。make_greeting の中で作られ、この関数が所有しています。関数が戻ると、ローカル変数はすべて破棄(drop)されます。返そうとしている &str は、もう存在しない String の中を指すことになってしまうのです。

アパートを引き払ったあとで、友だちに部屋の鍵を渡すようなものです。鍵は本物ですが、部屋はもう空っぽか、別の人が住んでいます。友だちに必要なのは鍵ではなく、家具のほうです。

よくある流れはこうです。まず &str を返す関数で greeting(String)をそのまま返して E0308(mismatched types)が出ます。するとコンパイラが「consider borrowing here: &greeting」(ここで借用してはどうか)と提案してきます。この提案で型の不一致は直りますが、そのまま E0515 にたどり着いてしまいます。本当に直すべきなのは、戻り値の型のほうです。

参照は、指している値より長く生きてはいけません。ローカル変数への参照を返すと、ダングリング参照(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: OK
404: Not Found
418: Unknown
▶ 実際に試してみよう

format! の結果をシグネチャに &'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 のページで説明しています。

ローカルな 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 を返します。

理解度チェック

1. format! でメッセージを作り、&message を返す関数があります。たいていの場合、正しい直し方はどれでしょう?

2. コンパイルできる関数はどれでしょう?

3. 戻り値の型として Cow<str> が向いているのはどんなときでしょう?

スコア: 0 / 3