コンテンツにスキップ

Rustのエラー E0106: missing lifetime specifier(ライフタイム指定子がない)

エラー E0106 は、関数の戻り値の型や構造体に参照(&)を書いたのに、それが何から借りたものなのか Rust にはわからない、という意味です。あなたのロジックが間違っているわけではありません。コンパイラは、あなたからもうひと言だけ情報がほしいか、ちょっとした方針の変更を求めているだけです。Rust を始めて最初の1週間で、ほとんどの人がこのエラーに出会います。そして、たいていは1行直すだけで解決します。(このページは Rustのコンパイルエラー解説 の一部です。)

error[E0106]: missing lifetime specifier
--> src/main.rs:1:32
|
1 | fn longer(a: &str, b: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
| ++++ ++ ++ ++

「ライフタイム指定子がありません。戻り値は借用された値を含んでいますが、それが a と b のどちらから借りたものかがシグネチャに書かれていません」という意味です。help: には、名前付きのライフタイム 'a を加える案が示されています。

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

fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("apple", "fig"));
}

参照は借用です。つまり、ほかの誰かが所有しているデータを指しています。関数が参照を返すときは、それが誰のデータを指しているのかを Rust が知っている必要があります。そうすれば、呼び出し側が結果を使っている間、そのデータがちゃんと生きているかを確かめられるからです。

1行目の下にある3つの印を見てください。コンパイラは a と b に ---- を、戻り値の型に ^ を付けています。「あなたは借用した &str を返していますが、それは a から来たかもしれないし、b から来たかもしれません。どっちですか?」と聞いているのです。Rust は推測で決めたりはしません。

図書館の本にたとえてみましょう。友だちが本を手渡しながら「これ、借りてきたものなんだ」と言ったら、誰から借りたのかを知りたくなりますよね。いつ返さないといけないかがわかるからです。ライフタイムは、借用に付けるこの「貸し主のラベル」のようなものです。

Rust は、参照が指しているデータより長く生き残ることは絶対にない、と保証しています(いわゆる「ダングリングポインタ」がありません)。関数呼び出しについてこれを確かめるとき、コンパイラが見るのは関数のシグネチャだけで、中身(本体)は見ません。そのため、戻り値の参照がどこから来るのかは、シグネチャ自体に書かれていなければなりません。

とはいえ、ふだんライフタイムを書くことはほとんどありません。Rust が次の3つのシンプルな省略規則(elision rules)で自動的に補ってくれるからです。

  1. 参照の引数には、それぞれ別のライフタイムが付けられます。
  2. 参照の引数がちょうど1つなら、戻り値の参照はその引数から借りたものになります。
  3. &self か &mut self を持つメソッドなら、戻り値の参照は self から借りたものになります。

E0106 が出るのは、これらの規則のどれでも答えが決まらないときです。つまり、参照の引数が2つ以上ある(そして self がない)とき、参照の引数が1つもないとき、そして構造体に参照を入れたときです。詳しくはライフタイムのレッスンで、参照そのものについては借用のページで説明しています。

直し方1:名前付きのライフタイム('a)を付ける

Section titled “直し方1:名前付きのライフタイム('a)を付ける”

関数が本当に、引数のどれかの一部を返しているときはこれを選びましょう。'a はただの名前です。引数と戻り値の両方に書くと、「結果はこれらの引数と同じだけ生きる」という意味になります。

// 'a:返す &str は a か b から借りるので、両方と同じだけ生きる
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("apple");
let second = String::from("fig");
println!("長いほうの単語: {}", longer(&first, &second));
}

実行結果:

長いほうの単語: apple
▶ 実際に試してみよう

結果がいつも1つの引数からしか来ないなら、'a はその引数にだけ付けましょう。たとえば fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str> です。コンパイラの提案はすべてに 'a を付けますが、それでも動くものの、必要以上に制限が厳しくなります。

直し方2:代わりに所有権のある String を返す

Section titled “直し方2:代わりに所有権のある String を返す”

関数が新しい文字列を作るとき、または呼び出し側を借用に縛りつけたくないときはこれを選びましょう。所有権のある String は呼び出し側のものになるので、追跡するものが何もありません。

fn greeting(name: &str, lang: &str) -> String {
if lang == "es" {
format!("Hola, {name}!")
} else {
format!("Hello, {name}!")
}
}
fn main() {
println!("{}", greeting("Ana", "es"));
println!("{}", greeting("Sam", "en"));
}

実行結果:

Hola, Ana!
Hello, Sam!
▶ 実際に試してみよう

この方法では、小さなメモリ確保が1回増えます。ほとんどのプログラムでは、まったく問題ありません。気にしなくて大丈夫です。

直し方3:構造体には所有データを入れる

Section titled “直し方3:構造体には所有データを入れる”

title: &str のような構造体のフィールドでも、同じエラーが出ます。

error[E0106]: missing lifetime specifier
--> src/main.rs:2:12
|
2 | title: &str,
| ^ expected named lifetime parameter

構造体の場合、いちばんよい直し方はほぼいつも「データを所有する」ことです。&str の代わりに String を、&[T] の代わりに Vec<T> を使いましょう。そうすれば、構造体を好きなだけ長く使えます。

#[derive(Debug)]
struct Book {
title: String, // &str ではなく、所有する
pages: u32,
}
fn main() {
let book = Book { title: String::from("Dune"), pages: 412 };
println!("{}({}ページ)", book.title, book.pages);
}

実行結果:

Dune(412ページ)
▶ 実際に試してみよう

構造体にライフタイムを付けるのは、それがほかの場所にあるデータを短い間だけのぞくビューのときだけにしましょう。たとえば、大きなテキストを読んでいくパーサーのようなものです。その場合は、構造体とフィールドの両方に <'a> を書きます。

struct Excerpt<'a> {
text: &'a str, // 下の novel から借りる
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first };
println!("抜粋: {}", excerpt.text);
}

実行結果:

抜粋: Call me Ishmael
▶ 実際に試してみよう

直し方4:文字列リテラルには 'static を使う

Section titled “直し方4:文字列リテラルには 'static を使う”

関数に参照の引数が1つもないと、エラーは「借りてくる元になる値がない」と言います。

error[E0106]: missing lifetime specifier
--> src/main.rs:1:22
|
1 | fn default_name() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

"OK" のような文字列リテラルしか返さないなら、&'static str と書きましょう。リテラルはプログラム本体に埋め込まれているので、実行中ずっと生きています。

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
fn main() {
println!("200 の意味は {}", status_text(200));
println!("404 の意味は {}", status_text(404));
}

実行結果:

200 の意味は OK
404 の意味は Not Found
▶ 実際に試してみよう

'static を使うのは、本物のリテラルや定数のときだけにしましょう。実行中に作られる文字列(format! で作ったものや、ファイルから読んだもの)には 'static は役に立ちません。その場合は String を返します(直し方2)。

  • 2つの入力を受け取るヘルパー関数。たとえば fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str> です。直し方:'a で戻り値を map にだけ結びつけます(直し方1)。
  • 文字列を持つ構造体。たとえば、パースした入力から作る User { name: &str } です。直し方:フィールドを String にします(直し方3)。
  • 決まったメッセージを返す関数。たとえば fn error_label() -> &str です。直し方:&'static str にします(直し方4)。

メソッドについてはうれしいお知らせがあります。fn name(&self) -> &str は、規則3のおかげでライフタイムをまったく書かなくて済みます。

理解度チェック

1. fn first_word(s: &str) -> &str がライフタイムなしでコンパイルできるのはなぜでしょう?

2. 構造体 Config { path: &str } で E0106 が出ました。たいていの場合、いちばんよい直し方はどれでしょう?

3. format! でメッセージを作って返す関数があります。戻り値の型はどれにすべきでしょう?

スコア: 0 / 3