Rustでベクターの要素から指定したindexの値を取得しようとしてハマった話
cannot move out of a shared reference
Rust初心者です。上記のエラーでハマりました。
vectorの要素のうち指定したindexの値だけを取り出したい
Goでいうと以下のような処理です。
func get() Hoge {
vector := []Hoge{{name: "first"}}
return vector[0]
}
type Hoge struct {
name string
}
まあ普通にできます。Rustでこれと似たようなことをするとき、最初に以下のようなコード書きました。
fn get() -> Result<Hoge, String> {
let vector: Vec<Hoge> = vec![Hoge {
name: String::from("first"),
}];
if let Some(&first) = vector.get(0) {
return Ok(first);
}
Err("error".to_string())
}
struct Hoge {
name: String,
}
これはコンパイルエラーになります。
error[E0507]: cannot move out of a shared reference
--> src/main.rs:6:27
|
6 | if let Some(&first) = vector.get(0) {
| ----- ^^^^^^^^^^^^^
| |
| data moved here
| move occurs because `first` has type `Hoge`, which does not implement the `Copy` trait
調べた結果、原因は vector から0番目の要素の所有権を奪おうとしていることのようです。vector.get(0) は(存在すれば)vectorの0番目の要素の参照を返しますが、let Some(&first) = vector.get(0)で所有権を奪ってfirst を生成しようとしているのでエラーになるようです。
std::mem::replace を使う
vector[0]の値を別の値と入れ替えることはできます。
fn get() -> Result<Hoge, String> {
let mut vector: Vec<Hoge> = vec![Hoge {
name: String::from("first"),
}];
let first = std::mem::replace(
&mut vector[0],
Hoge {
name: String::from(""),
},
)
return Ok(first);
}
vector[0] を取り出し、代わりに新しい構造体をセットしています。これでコンパイルが通りました。
個人的には、元の配列の要素をSome でくくり、replace 時にNone で置き換えたほうが良いかなと思いました。理由は以下です。
- 代わりの要素を生成するのが面倒
- 要素のstructが複雑な場合は無駄にコストがかかる
- 2回目以降のアクセスで
Noneが返ってくるので参照済みかの判定ができる
fn get() -> Result<Hoge, String> {
let mut vector = vec![Some(Hoge {
name: String::from("first"),
})];
if let Some(first) = std::mem::replace(&mut vector[0], None) {
return Ok(first);
}
Err("error".to_string())
}
ちなみに、最初のエラーメッセージに書いてありますが、要素の型にCopy トレイトが実装されていれば、vector.get(0)の方法でもできるらしいです。取り出すというより、元の値をコピーするみたいです。
fn get() -> Result<Hoge, String> {
let vector: Vec<i64> = vec![1];
if let Some(&first) = vector.get(0) {
return Ok(Hoge {
name: first.to_string(),
});
}
Err("error".to_string())
}
Rustでは他の言語で普通にできることができなくで躓きがちです。難しい…