rustでツールを作成する際にはまった箇所をメモしておく。
以下のドキュメントを参照しながら自分の理解をまとめる。
The Rust Programming Language 日本語版 - The Rust Programming Language 日本語版
何にはまったのか?
結論から言うと、値の所有権を理解していなかったためコンパイルに失敗した。
自分がコンパイルエラーを出したコードは以下のような構造になっていた。
struct Sample{ name: String, num: u32, } fn main() { let mut samples: Vec<Sample> = Vec::new(); for i in 1..4{ samples.push(Sample{ name: String::from("sample"), num: i, }); } for sample in samples{ println!("sample num:{}", sample.num); } println!("sample[2] name:{}, num:{}", samples[2].name, samples[2].num); }
コンパイルエラーが以下。
Compiling move-sample v0.1.0 (/root/git/move-sample) error[E0382]: borrow of moved value: `samples` --> src/main.rs:19:43 | 7 | let mut samples: Vec<Sample> = Vec::new(); | ----------- move occurs because `samples` has type `Vec<Sample>`, which does not implement the `Copy` trait ... 15 | for sample in samples{ | ------- `samples` moved due to this implicit call to `.into_iter()` ... 19 | println!("sample[2] name:{}, num:{}", samples[2].name, samples[2].num); | ^^^^^^^ value borrowed here after move | note: `into_iter` takes ownership of the receiver `self`, which moves `samples` --> /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library/core/src/iter/traits/collect.rs:271:18 help: consider iterating over a slice of the `Vec<Sample>`'s content to avoid moving into the `for` loop | 15 | for sample in &samples{ | +
これはエラーメッセージの通り、「move occurs because samples
has type Vec<Sample>
, which does not implement the Copy
trait」であり、解決手段は「for sample in &samples{」なのだが、これがどういうことなのか初心者なりに調べてみた。
値の所有権
rustの値には所有権という概念が存在する。ドキュメントによると以下のルールである。
所有権規則
- Rustの各値は、所有者と呼ばれる変数と対応している。
- いかなる時も所有者は一つである。
- 所有者がスコープから外れたら、値は破棄される。
例えば、以下のように書くと値「1」の所有者は「num」ということになる。
let num:u32 = 1;
値のcopyとmove
以下のように書くと変数xの値が変数yにコピーされ、yとxどちらを参照しても同じ値が入っているというのは、どの言語でもほとんど共通の処理だと思う。
x = 1; y = x;
上記はrustでも同様の意味になる。これをcopyという。
だが、rustでは同じ意味にならないことがある。それが以下の例だ。
let s1 = String::from("test"); let s2 = s1;
この場合は、s1の値はs2にコピーされない。所有者s1が持っていた値は所有者s2のものとなる。これをmoveという。
値の所有者は一つというルールがあるので、この処理の後にs1は値を所有していない。そのため、これ以降の処理でs1を参照しようとするとコンパイルエラーになる。
また所有者の変更は代入のように記述した場合だけではない。関数に値を渡したときやforループでinto_iter()で値を渡した時にも所有者の変更が行われる。
moveかcopyか
=で値のやり取りをする場合でも数値を扱う場合はcopyで、文字列を扱う場合はmoveと、意味合いが異なっているが、それは扱う値の型に依存している。ドキュメント曰く以下の型はcopyを使用できる。
- あらゆる整数型。u32など。
- 論理値型であるbool。trueとfalseという値がある。
- あらゆる浮動小数点型、f64など。
- 文字型であるchar。
- タプル。ただ、Copyの型だけを含む場合。例えば、(i32, i32)はCopyだが、 (i32, String)は違う。
今回の失敗
今回のコンパイルエラーの原因は、copyできない値をforで処理しようとしたことだ。
以下のコードでは、暗黙的にinto_iter()が呼び出され、samplesの各要素の参照ではなく値を返す処理が行われてしまう。
for sample in samples{
そのため以下で参照しようと際に 値を所有していないのでコンパイルエラーになった。
println!("sample[2] name:{}, num:{}", samples[2].name, samples[2].num);
修正
これの修正はコンパイルエラーにも記述されているが、各要素の参照を返すように記述すればよい。
for sample in &samples{
まとめ
コンパイルエラーを通して、rustのmoveとcopyの概念を理解した()。
この記事を書いた後で気が付いたが、rustの所有権に関する記事はQiitaなどにいくつもあるようだ。
それだけ所有権の話はrustを使う上で重要という事なのだろう。