コンテンツにスキップ

Rustのエラー E0507: cannot move out of a value behind a shared reference(借用中の値のムーブ)

E0507 は、借りているだけのものから、値を持ち去ろう(ムーブしよう)とした、という意味のエラーです。借りた値を見ることはできますが、持っていくことはできません。持っていってしまったら、本当の持ち主のところに穴が空いてしまうからです。とてもよく出るエラーで、とくに Vec<String>、構造体のフィールド、Option でよく見かけます。直し方はたいてい、借用に変えるか .clone() するかです。このページでは、まず直し方を見せてから、それぞれを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0507]: cannot move out of `self.name` which is behind a shared reference
--> src/main.rs:7:9
|
7 | self.name // ❌ self が借りているだけの String を渡そうとしている
| ^^^^^^^^^ move occurs because `self.name` has type `String`, which does not implement the `Copy` trait
|
help: consider cloning the value if the performance cost is acceptable
|
7 | self.name.clone() // ❌ self が借りているだけの String を渡そうとしている
| ++++++++

1行目は「共有参照の向こうにある self.name をムーブして取り出すことはできない」という意味です。help は「性能のコストが許せるなら、値を clone してはどうか」と提案しています。

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

struct User {
name: String,
}
impl User {
fn name(&self) -> String {
self.name // ❌ self が借りているだけの String を渡そうとしている
}
}
fn main() {
let u = User { name: String::from("Ferris") };
println!("{}", u.name());
}

同じエラーコードでも、文言はいくつかあります。どれも「借りているだけなので、ムーブはできない」という意味です。

  • let first = names[0]; では cannot move out of index of `Vec<String>`
  • r: &String のときの let owned = *r; では cannot move out of `*r` which is behind a shared reference
  • match *nick { Some(n) => n, ... } では cannot move out of `nick` as enum variant `Some` which is behind a shared reference
  • cannot move out of `report`, a captured variable in an `Fn` closure(Fn クロージャがキャプチャした変数をムーブしようとした)
  • cannot move out of `self.log` which is behind a mutable reference(そう、&mut でもそのまま持っていくことはできません)
  • let s: String = *shared; では cannot move out of an `Rc`

7行目を見てください。このメソッドは String、つまり所有された値を返します。なので self.name と書くと、文字列を構造体からムーブして、呼び出し元に渡すことになります。ところが、このメソッドが持っているのは &self、つまりユーザーへの共有(読み取り専用)の借用だけです。ユーザーを所有していないので、ユーザーの名前を人にあげることはできません。

move occurs because ... does not implement the Copy trait(Copy トレイトを実装していないのでムーブが起きる)の部分も大事です。もしフィールドが u32 だったら、Rust は黙ってコピーしてくれるので、エラーにはなりません。String はヒープのメモリを持っているので、コピーではなくムーブしかできないのです。

友だちの本棚を午後だけ借りたようなものです。棚にある本はどれでも読めます。でも、本を家に持って帰ることはできません。本棚はあなたのものではないし、友だちが戻ってきたら、本があったところが空っぽになっているからです。

どの値にも所有者はちょうど1人で、所有者がいなくなるときに値を片付けます(所有権を見てください)。もし借りている User から self.name をムーブできたら、ユーザーはまだその文字列を持っているつもりのままです。あとでユーザーが破棄されると、文字列をもう一度解放してしまったり、もう存在しないフィールドを誰かが読んでしまったりします。これは「解放後の使用」(use after free)や「二重解放」(double free)と呼ばれる、C や C++ でもっとも厄介なバグの2つです。

Vec も同じです。names[0] の持ち主は、あくまでベクタです。もし let first = names[0]; で値がムーブされたら、ベクタのインデックス 0 に穴が空き、ベクタが破棄されるときに、そこをもう一度解放しようとしてしまいます。だから、インデックスでアクセスしても手に入るのは中身へのアクセスだけで、所有権ではありません。

ルールを1行で言うと、「参照を通してできるのは、読むことと(&mut なら)書き換えることまで。持っていくことはできない」です。所有権がほしいなら、入れ物そのものを所有するか、値のあった場所に代わりのものを置いていく必要があります。

直し方1:ムーブせずに借用する

Section titled “直し方1:ムーブせずに借用する”

値を読むだけでいいときは、これを選びましょう。ほとんどの場合、これが正しい直し方です。String の代わりに &str を返す、v[0] の代わりに &v[0] と書く、Option の中身は as_deref() や as_ref() で借用する、といった具合です。

struct User {
name: String,
nickname: Option<String>,
}
impl User {
fn name(&self) -> &str {
&self.name
}
fn display_name(&self) -> &str {
match self.nickname.as_deref() {
Some(nick) => nick,
None => &self.name,
}
}
}
fn describe(nick: &Option<String>) -> &str {
match nick {
Some(n) => n,
None => "名無し",
}
}
fn main() {
let users = vec![
User { name: String::from("Ferris"), nickname: Some(String::from("カニ")) },
User { name: String::from("Corro"), nickname: None },
];
let first = &users[0];
println!("最初のユーザー: {}", first.name());
for user in &users {
println!("表示名: {}", user.display_name());
}
println!("ニックネーム: {}", describe(&users[1].nickname));
}

実行結果:

最初のユーザー: Ferris
表示名: カニ
表示名: Corro
ニックネーム: 名無し
▶ 実際に試してみよう

describe を見てください。*nick ではなく、nick(参照)に対して match しています。こうすると n は &String、つまり借用になり、何もムーブされません。match *nick { Some(n) => ... } と書くと、n に所有された String を求めることになり、E0507 になります。コンパイラのヒントも consider removing the dereference here(ここの参照外しを外してはどうか)と、同じことを言っています。

as_deref() は &Option<String> を Option<&str> に変え、as_ref() は Option<&String> を返します。.unwrap()、.map()、match の前にこれを使えば、所有された値ではなく借用を扱えます。r: &String のときの *r も同じで、たいていは * がまったく必要なく、r をそのまま使えば大丈夫です。詳しくはOptionと借用を見てください。

自分専用のコピーが本当に必要なときは、これを選びます。たとえば、元の値はそのままにして、別の場所にしまっておきたいときです。コンパイラも、ほとんどすべての E0507 のメッセージでこれを提案しています。

struct User {
name: String,
}
impl User {
fn name(&self) -> String {
self.name.clone()
}
}
fn main() {
let names = vec![String::from("Ferris"), String::from("Corro")];
let first = names[0].clone();
println!("先頭は{first}、リストにはまだ{}人の名前があります", names.len());
let u = User { name: String::from("Ferris") };
let mut badge = u.name();
badge.push_str("(管理者)");
println!("{badge} / {}", u.name);
}

実行結果:

先頭はFerris、リストにはまだ2人の名前があります
Ferris(管理者) / Ferris
▶ 実際に試してみよう

名前や短いリストを clone するのは安上がりで、まったく問題ありません。学んでいる最中ならなおさらです。問題になるのは、大きなデータを何度も回るループの中で clone するときだけです。借用(直し方1)で足りるなら、そちらを選びましょう。

直し方3:取り出して、代わりのものを置いていく

Section titled “直し方3:取り出して、代わりのものを置いていく”

&mut でアクセスできて、値を本当にムーブして取り出したいときは、これを選びます。たとえば、書き終わったログや次のタスクを誰かに渡したいときです。穴を空けることはできませんが、代わりのものと入れ替えることはできます。

  • std::mem::take(&mut x) は値を取り出し、代わりにデフォルト値を置きます(String なら ""、Vec なら空のベクタ)。
  • option.take() は Option の中身を取り出し、代わりに None を置きます。
  • std::mem::replace(&mut x, new) は new を入れて、古い値を返してくれます。
use std::mem;
#[derive(Debug)]
enum Status {
Queued,
Running,
}
struct Job {
log: String,
next_step: Option<String>,
status: Status,
}
impl Job {
fn take_log(&mut self) -> String {
mem::take(&mut self.log)
}
fn start_next(&mut self) -> Option<String> {
self.next_step.take()
}
fn set_status(&mut self, new: Status) -> Status {
mem::replace(&mut self.status, new)
}
}
fn main() {
let mut job = Job {
log: String::from("ビルド成功"),
next_step: Some(String::from("デプロイ")),
status: Status::Queued,
};
let old = job.set_status(Status::Running);
println!("状態: {old:?} -> {:?}", job.status);
println!("次のステップ: {:?}", job.start_next());
println!("もう一度次のステップ: {:?}", job.start_next());
println!("ログ: {:?}", job.take_log());
println!("今のログ: {:?}", job.log);
}

実行結果:

状態: Queued -> Running
次のステップ: Some("デプロイ")
もう一度次のステップ: None
ログ: "ビルド成功"
今のログ: ""
▶ 実際に試してみよう

構造体に穴が空くことは一度もありません。take_log のあとのログは空文字列に、start_next のあとのステップは None になっています。これが「cannot move out of self.x which is behind a mutable reference」(可変参照の向こうにある self.x をムーブして取り出せない)の定番の直し方です。mem::take を使うには、その型が Default を実装している必要があります。そうでない型には mem::replace を使いましょう。

直し方4:コレクションを所有してから、ムーブして取り出す

Section titled “直し方4:コレクションを所有してから、ムーブして取り出す”

コレクションや構造体をもう使い終わって、その中身がほしいときは、これを選びます。Vec を自分が所有していれば、remove(i)、swap_remove(i)、pop() で要素を渡してもらえますし、全部まとめてなら into_iter() が使えます。&self ではなく self を受け取るメソッドは構造体を所有しているので、フィールドをムーブして取り出せます。

struct User {
name: String,
}
impl User {
fn into_name(self) -> String {
self.name
}
}
fn main() {
let mut queue = vec![String::from("メール"), String::from("バックアップ"), String::from("レポート")];
let first = queue.remove(0);
println!("{first}を実行中、残り: {queue:?}");
let users = vec![User { name: String::from("Ferris") }, User { name: String::from("Corro") }];
let names: Vec<String> = users.into_iter().map(|u| u.into_name()).collect();
println!("{names:?}");
}

実行結果:

メールを実行中、残り: ["バックアップ", "レポート"]
["Ferris", "Corro"]
▶ 実際に試してみよう

into_iter() はベクタを消費するので、そのあと users は使えません(使うと E0382 になります)。into_name の into_ という接頭辞は Rust の命名の習慣で、「このメソッドは値を使い切る」ことを読む人に伝えています。remove(0) は残りの要素を全部前にずらします。順番を気にしないなら、swap_remove のほうが速いです。詳しくは要素を削除するを見てください。

&users を for ループで回しながらフィールドを集めるとき。 for u in &users { names.push(u.name); } は cannot move out of `u.name` which is behind a shared reference になります。直し方:names.push(u.name.clone()) とするか、あとで users を使わないなら for u in users でループするか、借用を集めます(users.iter().map(|u| u.name.as_str()).collect::<Vec<&str>>())。

参照を通して Option のフィールドに .unwrap() を呼ぶとき。 config: &Config のとき、config.title.unwrap() はエラーになります。unwrap は Option を値で(所有権ごと)受け取るからです。コンパイラが直し方を教えてくれます。config.title.as_ref().unwrap()(&String が手に入る)か、Option<&str> がほしいなら config.title.as_deref() です。

Fn クロージャの中で、キャプチャした変数をムーブするとき。 何度も呼ばれるかもしれないクロージャは、キャプチャしたものを人にあげることができません。cannot move out of `report`, a captured variable in an `Fn` closure になります。直し方:クロージャの中では借用する(&report)か、中で clone するか、1回しか呼ばないならトレイト境界を FnOnce に変えます。詳しくはFn・FnMut・FnOnceを見てください。

Rc を参照外しするとき。 let s: String = *shared; は cannot move out of an `Rc` になります。ほかの所有者がまだその値を使っているかもしれないからです。直し方:借用する(&*shared か、単に &shared)か、clone するか、Rc::unwrap_or_clone(shared) を使います。Box は違います。Box は自分が所有しているので、let s = *boxed; と書けます。詳しくはスマートポインタを見てください。

  • E0382:ムーブされた値の使用。ムーブすること自体は許されていたのに、そのあとでもう一度値を使った。
  • E0594:& 参照を通して代入できない。「借りているだけ」という同じルールの、ムーブではなく書き込み版です。
  • E0596:可変で借用できない。たとえば、&self しか持っていないメソッドで mem::take(&mut self.x) を使ったとき。
  • E0308:型の不一致、expected String, found &String。借用に切り替えた直後によく出ます。&str を返すか、.clone() を呼びましょう。
  • E0515:ローカル変数への参照を返せない。借用で直そうとしたら、もうすぐ破棄されるデータを指していた、というときに出ます。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. names: Vec<String> のとき、let first = names[0]; は E0507 になります。first を表示するだけなら、ふつういちばんよい直し方はどれですか?

2. メソッド fn next(&mut self) -> Option<Task> で、self.pending(Option<Task>)を渡したいとします。コンパイルできるのはどれですか?

3. nums: Vec<i32> なら let n = nums[0]; で問題ないのに、Vec<String> ではエラーになるのはなぜですか?

スコア: 0 / 3