Rust 'cannot return value referencing local variable' (E0515) の修正方法

intermediate🦀 Rust2026-06-01| Rust(全バージョン)、rustc、Cargo — 全プラットフォーム(Linux、macOS、Windows)

Error Message

error[E0515]: cannot return value referencing local variable `result`
#rust#ライフタイム#参照#借用#戻り値

エラーの内容

error[E0515]: cannot return value referencing local variable `result`
 --> src/main.rs:5:5
  |
5 |     &result
  |     ^------
  |     ||
  |     |`result` is borrowed here
  |     returns a value referencing data owned by the current function

このエラーは、関数自身が所有しているものへの参照を返そうとしたことを意味します。関数が返った瞬間、そのローカル変数はドロップされます。そのため、その変数への参照は解放済みメモリを指すことになります。Rustの借用チェッカーは、コードが実行される前にこの問題を検出します。

根本原因

ローカル変数は、それが属する関数が返ると同時に消滅します。それだけです。Rustのルールはシンプルです。参照は、それが指すデータよりも長生きできません。ローカル変数への参照を返そうとすると、ダングリングポインタが発生します。これはCプログラムをクラッシュさせ、メモリを静かに破壊するバグの一種です。Rustはそのようなコードのコンパイルを拒否します。

典型的なケースは次のようになります:

fn get_greeting(name: &str) -> &str {
    let result = format!("Hello, {}!", name); // result はローカルな String
    &result // ERROR: result はここでドロップされ、参照はダングリングになる
}

コンパイラエラー:

error[E0515]: cannot return value referencing local variable `result`
 --> src/main.rs:3:5
  |
3 |     &result
  |     ^------
  |     |`result` is borrowed here
  |     returns a value referencing data owned by the current function

修正方法(ステップごと)

修正1:参照の代わりに所有型を返す

関数がデータを生成する場合は、そのまま返しましょう。参照は不要です。

// 修正前(壊れている)
fn get_greeting(name: &str) -> &str {
    let result = format!("Hello, {}!", name);
    &result // E0515
}

// 修正後(正しい)
fn get_greeting(name: &str) -> String {
    format!("Hello, {}!", name) // 所有している String を直接返す
}

コレクションでも同じパターンが適用されます:

// 修正前(壊れている)
fn even_numbers(input: &[i32]) -> &[i32] {
    let result: Vec<i32> = input.iter().filter(|&&x| x % 2 == 0).cloned().collect();
    &result // E0515
}

// 修正後(正しい)
fn even_numbers(input: &[i32]) -> Vec<i32> {
    input.iter().filter(|&&x| x % 2 == 0).cloned().collect()
}

修正2:入力パラメータへの参照を返す

新しいデータを生成せず、入力から選択やスライスを行うだけであれば、入力を直接参照しましょう。ローカルコピーは不要です。

// 修正前(壊れている — 不必要なローカルコピーを作成している)
fn first_word(s: &str) -> &str {
    let owned = s.to_string(); // 不要なローカル変数
    owned.split_whitespace().next().unwrap_or(&owned) // E0515
}

// 修正後(正しい — 入力を直接参照する)
fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
    // 戻り値のライフタイムは `s` に紐付けられており、問題ない
}

修正3:両方が必要な場合は Cow<'_, str> を使う

借用した値を返す場合と新しく生成した値を返す場合が混在する関数があります。Cow(Clone-on-Write)はまさにこのケースのために設計されています:

use std::borrow::Cow;

fn sanitize(input: &str) -> Cow<'_, str> {
    if input.contains('<') {
        Cow::Owned(input.replace('<', "&lt;")) // 新しく生成した String
    } else {
        Cow::Borrowed(input) // 元の値を借用するだけ
    }
}

fn main() {
    let clean = sanitize("hello <world>");
    println!("{}", clean); // hello &lt;world>
}

修正4:関数内でデータを生成しないように再構成する

本質的な修正は、不要なアロケーションを削除することです。ローカルの String を生成してスライスするだけの関数は、多くの場合 &str 入力に対して直接操作できます:

// 修正前(壊れている)
fn get_domain(url: &str) -> &str {
    let trimmed = url.trim_start_matches("https://").to_string(); // ローカル String
    trimmed.split('/').next().unwrap_or("") // E0515
}

// 修正後 — &str 入力に対して直接 trim を行い、ローカル String は不要
fn get_domain(url: &str) -> &str {
    url.trim_start_matches("https://")
       .trim_start_matches("http://")
       .split('/')
       .next()
       .unwrap_or("")
}

修正の確認

cargo build を実行してください。E0515 が消えていれば完了です。また、cargo test を実行して何も壊れていないことを確認しましょう:

cargo build
cargo test

より素早い反復作業には、cargo check がコード生成をスキップしてフルビルドの約半分の時間で型エラーとライフタイムエラーを検出します:

cargo check

判断の目安

  • 関数内で新しいデータを生成している? → 所有型(StringVec<T> など)を返す
  • 入力から選択またはスライスするだけ? → 入力パラメータへの参照を返す
  • 生成する場合としない場合が混在している?Cow<'_, str> または Cow<'_, [T]> を使う
  • 呼び出し元が結果を変更する必要がある? → 常に所有型を返す

よくある間違い

ライフタイムアノテーションを追加しても、この問題は解決されません。非常によく見られる試みです:

// まだ壊れている — ライフタイムアノテーションは変数のライフタイムを延長しない
fn get_greeting<'a>(name: &'a str) -> &'a str {
    let result = format!("Hello, {}!", name);
    &result // E0515 — アノテーションでローカル変数を救うことはできない
}

ライフタイムアノテーションは、既存のライフタイム間の関係を記述するものです。新しいライフタイムを生成したり、ローカル変数のスコープを延長したりすることはできません。データは参照よりも物理的に長く存在しなければなりません。アノテーションでその事実は変わりません。

Related Error Notes