配列状で管理している自作の構造体の値の変更が反映されないということがあった。
結果的には Copy
トレイトを追加してしまっていたのが原因だった。
自作の構造体に対して、特に意識せずに #derive(Copy)
でコピートレイトを指定:
|
していた。
その複数個を配列で、 Option
での有無ありで管理:
let mut foos: [Option<Foo>; 3] = [ |
していて、ループで更新処理を行っていた:
for p in foos.iter_mut() { |
ここでちょっと色気を出して、 filter()
で None
をはじいておいて unwrap()
を使って if let Some(foo) = ...
のインデントを減らそうかとしてみた:
for p in foos.iter_mut().filter(|x| x.is_some()) { |
( flat_map
を使わないのは、更新した結果によっては None
にクリアするケースがあったため。)
したら値が更新されなくなって結構悩んだ
(Rustをよく理解してなくて雰囲気でいじってるのが問題なんだけど…)。
原因は (1) の unwrap()
での Some
値の取り出しで Foo
の値のコピーが発生していて、新しいインスタンスに対して変更してしまっていた。
Foo
構造体に Copy
トレイトを指定していなければエラー:
error[E0507]: cannot move out of `*p` which is behind a mutable reference |
が出て、 as_ref()
を使ってみろと教えてくれるので時間を無駄にせずに済んだ…。
実際には書き換えをしたいので as_mut()
を使ってやる:
let mut foo = p.as_mut().unwrap(); // -- (1の修正)、 let foo = ...でもよい、がmutあってもワーニングは出ない |
as_mut()
で &mut Option<Foo>
から Option<&mut Foo>
に変換できて、移動(またはコピー)じゃなく mut
の借用になり、元の値の更新ができる。
結論
Copy
トレイトを実装してしまうとついうっかりコピーが発生してしまう可能性があるので、 必要なければ追加しないこと(特にstruct
には)- ローカル変数は型宣言を省略できるので、よく理解してないと意図してない型を扱ってる可能性がある