以下のようなプログラムがRustでコンパイルできず、ちょっと悩んだのでメモ。
皆さんは、以下のプログラムをぱっと見てエラーの理由が分かるでしょうか?
struct Banana {
vals: Vec<usize>,
}
impl Banana {
fn cut(&mut self) {
println!("cut");
// 本来はここで self.vals の値を変更する
}
}
fn test(a: &mut Banana) {
for v in a.vals.iter() { // --- (*1)
println!("{}", v);
a.cut(); // --- (*2) ここでエラーが出る!!
}
}
fn main() {
let mut a = Banana{vals:vec![1,2,3]};
test(&mut a);
}
コマンドライン上で $rustc b.rs
を実行するとエラーが出て動きません。
error[E0502]: cannot borrow `*a` as mutable because it is also borrowed as immutable
--> b.rs:14:9
|
12 | for v in a.vals.iter() {
| -------------
| |
| immutable borrow occurs here
| immutable borrow later used here
13 | println!("{}", v);
14 | a.cut();
| ^^^^^^^ mutable borrow occurs here
error: aborting due to previous error
なぜ動かないのか?
エラーを日本語でよくよく確認すると「*a
が不変としても借用されるため、不変として借用することはできません」とのこと。
(*1)でa.vals.iter()
を実行することで、aがイミュータブル(不変)な借用として使われるのに、(*2)でcutを呼び出すことで、aを&mut a
(可変な参照)として扱おうとしているためエラーが出るのです。
つまり、同じスコープ内で、変数をミュータブルとイミュータブルの両方として利用するのが問題なのです。
簡単な修正例1
そこで、動作がちょっと変わってしまいますが、関数cutを呼び出すタイミングを以下のようにforループ外に移動すれば動きます。
fn test(a: &mut Banana) {
for v in a.vals.iter() {
println!("{}", v);
// a.cut(); ループ外に出す
}
a.cut();
}
簡単な修正例2
あるいは、もしも可能であるなら関数cutの引数を&mutから&に変更します。これにより、イミュータブルな借用になります。
impl Banana {
fn cut(&self) {
println!("cut");
}
}
また、コメント欄で @namn1125 さんがより完全な修正例を出してくださっています。ありがとうございます!
ボローチェッカーは優秀なのですが、時によく考えないと修正方法が分からないことも多いです。
for文との組み合わせにも注意しましょう。
参考
以下で、エラーメッセージと解決のヒントというPDFを無料で配布しています。良かったら見てください!