Rustのエラー E0507: cannot move out of a value behind a shared reference(借用中の値のムーブ)
E0507 は、借りているだけのものから、値を持ち去ろう(ムーブしよう)とした、という意味のエラーです。借りた値を見ることはできますが、持っていくことはできません。持っていってしまったら、本当の持ち主のところに穴が空いてしまうからです。とてもよく出るエラーで、とくに Vec<String>、構造体のフィールド、Option でよく見かけます。直し方はたいてい、借用に変えるか .clone() するかです。このページでは、まず直し方を見せてから、それぞれを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”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 referencematch *nick { Some(n) => n, ... }ではcannot move out of `nick` as enum variant `Some` which is behind a shared referencecannot 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`
エラーの意味
Section titled “エラーの意味”7行目を見てください。このメソッドは String、つまり所有された値を返します。なので self.name と書くと、文字列を構造体からムーブして、呼び出し元に渡すことになります。ところが、このメソッドが持っているのは &self、つまりユーザーへの共有(読み取り専用)の借用だけです。ユーザーを所有していないので、ユーザーの名前を人にあげることはできません。
move occurs because ... does not implement the Copy trait(Copy トレイトを実装していないのでムーブが起きる)の部分も大事です。もしフィールドが u32 だったら、Rust は黙ってコピーしてくれるので、エラーにはなりません。String はヒープのメモリを持っているので、コピーではなくムーブしかできないのです。
友だちの本棚を午後だけ借りたようなものです。棚にある本はどれでも読めます。でも、本を家に持って帰ることはできません。本棚はあなたのものではないし、友だちが戻ってきたら、本があったところが空っぽになっているからです。
Rust がこうする理由
Section titled “Rust がこうする理由”どの値にも所有者はちょうど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と借用を見てください。
直し方2:clone する
Section titled “直し方2:clone する”自分専用のコピーが本当に必要なときは、これを選びます。たとえば、元の値はそのままにして、別の場所にしまっておきたいときです。コンパイラも、ほとんどすべての 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 のほうが速いです。詳しくは要素を削除するを見てください。
よく出会う場面
Section titled “よく出会う場面”&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; と書けます。詳しくはスマートポインタを見てください。
関連するエラー
Section titled “関連するエラー”- E0382:ムーブされた値の使用。ムーブすること自体は許されていたのに、そのあとでもう一度値を使った。
- E0594:
&参照を通して代入できない。「借りているだけ」という同じルールの、ムーブではなく書き込み版です。 - E0596:可変で借用できない。たとえば、
&selfしか持っていないメソッドでmem::take(&mut self.x)を使ったとき。 - E0308:型の不一致、
expected String, found &String。借用に切り替えた直後によく出ます。&strを返すか、.clone()を呼びましょう。 - E0515:ローカル変数への参照を返せない。借用で直そうとしたら、もうすぐ破棄されるデータを指していた、というときに出ます。
- コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
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
さらに詳しく
Section titled “さらに詳しく”- E0507 — Rust error codes index(英語):
mem::replaceを使った直し方も含めた、公式の説明です。 - 所有権とは? — The Rust Programming Language 日本語版:このエラーの背景にある、ムーブ、
Clone、Copyの考え方です。 - 参照と借用 — The Rust Programming Language 日本語版:
&と&mutを通してできること、できないことです。 std::mem::take— 標準ライブラリのドキュメント(英語):&mutから値を取り出し、代わりにデフォルト値を置いていく関数です。兄弟分のmem::replaceなら、代わりに置く値を選べます。Option— 標準ライブラリのドキュメント(英語):as_ref、as_deref、takeが例付きで載っています。