rust: Rustがんばろう 9日目
はじめに
Rust を勉強することにした。
行き詰まる。
8章の宿題
8章のまとめ には課題というか宿題が書いてあった。
- 整数のリストが与えられ、ベクタを使ってmean(平均値)、median(ソートされた時に真ん中に来る値)、 mode(最も頻繁に出現する値; ハッシュマップがここでは有効活用できるでしょう)を返してください。
- 文字列をピッグ・ラテン(訳注: 英語の言葉遊びの一つ)に変換してください。各単語の最初の子音は、 単語の終端に移り、”ay”が足されます。従って、”first”は”irst-fay”になります。ただし、 母音で始まる単語には、お尻に”hay”が付け足されます(“apple”は”apple-hay”になります)。 UTF-8エンコードに関する詳細を心に留めておいてください!
- ハッシュマップとベクタを使用して、ユーザに会社の部署に雇用者の名前を追加させられるテキストインターフェイスを作ってください。 例えば、”Add Sally to Engineering”(開発部門にサリーを追加)や”Add Amir to Sales”(販売部門にアミールを追加)などです。 それからユーザに、ある部署にいる人間の一覧や部署ごとにアルファベット順で並べ替えられた会社の全人間の一覧を扱わせてあげてください。
なかなか終わらない・・・。
特に時間がかかっているのは 3番目の「アルファベット順に」というところ。
値は HashMap<String, Vec<String>> にした。
key は部署名、value は名前のベクタだ。
追加は insert() すればよい。
部署名でソートするのは、一旦 map.iter().collect() で Vec<(&String, &Vec<String>)> にして sort() すればよい。
タプルのソートだけだと不安なので sort_by(|p, q| p.0.cmp(q.0)) みたいにして key で前者と後者を比較した。
せっかくなので名前の方もソートしたいのだが、ここで詰まっている。
collect() で作ったベクタは value が mut ではない参照になっている。
なので list.1.sort() などとすると mut じゃないのでできないと怒られる。
整理していこう。
HashMap のソート
「rust hashmap ソート」で検索するとここら辺が出てきた。
そのままだとソートできないので iter() でイテレータを取ってきて collect() でベクタにしてソートするなり BTreeMap を使うなりするそうだ。
iter() は (&'a K, &'a V) という形でグリグリ取ってこれるようにしてくれる。
Iter は構造体だ。
collect() は Vec にするメソッドではなく、左辺が Vec だったから Vec にしているだけのようだ。
なので Vec<_> で中身は何でもよいけど外側は Vec であることを伝えないといけないのね。
じゃあ、(&'a K, &'a V) がではなく (&mut 'a K, &mut 'a V) になればよいのでは?
iter_mut()
iter() の下に iter_mut() があった。
これは (&'a K, &'a mut V) のイテレータにしてくれる。
key は mut にしないのはなんでだろう?
単に HashMap の key が変わったら都合が悪いというか、ダメだろうというか、そういう感じか。
Vec<(&String, &Vec<String>)> が Vec<(&mut String, &mut Vec<String>)> になったので sort() が使えるようになった。
中身は String なので sort_by() でなくてもいいだろう(宿題だから適当)。
mut 外し
ではソート済みを元の Vec<(&String, &Vec<String>)> に戻そうとしたのだが型が違うと怒られる。
mut 無しにする方向は安全だから良い、とかいう話ではないのか。。。
タプルだし、仕方ないといえば仕方ない気もしてきた。
今回はタプルの中なので iter() を使っても mut が外れたりしない。
あれは HashMap の key-value をシリアライズするするからああいうことができたんだろう。
あきらめて Vec<(&String, &Vec<String>)> の変数に push() していくことにした。
値をスコープの外に持って行けない
課題として、文字列で命令して追加したりソートしたりするので、処理は match で分岐した先でやっている。
「出力する」という内容も、処理過程はそれぞれだが Vec<(&String, &Vec<String>)> を出力するということにして match の外側で出力するようにした。
そうすると match の中で値を作りたくなる。
Vec::new() で作って push() したり、
タプルを push() するために String::from(key) で引数でとってきた &str を String にしたりしたのだが、
ことごとく「一時的な値だ」ということで返せない。
たとえばこうすると “world” の行でエラーになる。
let mut v = Vec::new();
let k = &"hello".to_string();
v.push((k, k));
{
let k = &"world".to_string(); // エラー
v.push((k, k));
}
println!("{:?}", v);
temporary value dropped while borrowed consider using a
letbinding to create a longer lived value
何も考えずに GitHub Copilot に修正してもらうと、こうだ。
let mut v = Vec::new();
let k = &"hello".to_string();
v.push((k, k));
let world = "world".to_string();
{
let k = &world;
v.push((k, k));
}
println!("{:?}", v);
まあそうなんだけど、そうじゃないよなあ。
きっとライフタイムというものがそういうのをやるのだろうが、調べるのはその時になってからで良いかな。
よってこれが今の私の限界だ。
参照外し
何だかわからないのがこれ。
// s: Vec<&str>
let cmd: String = s[0].to_lowercase();
match &*cmd {
"add" => self.req_add(&s[1..]),
"print" => self.req_print(&s[1..]),
_ => println!("Unknown command: {}", s[0]),
}
cmd は String だ。
なのに * を付けてさらに & まで付けている。
これを付けないと “add” と “print” の行で “expected String, found &str” というエラーになる。
文字列のプリミティブ型は &str なのでその主張は分かる。
では、と String::from() や .to_string() をここで使ってもエラーになる。
エラーの意味は分からんが、ここに書いたらいかんだろうなあという気持ちにはなるのでよいのだ。
ではこの文より前に let add = String::from("add"); みたいに変数を用意したいかというと、すれば解決はするのだがしたくない。
どうしたらいいんだ?と探した結果が &* だったのだ。
* は参照外し演算子(dereference operator)で、& の反対の意味を持つ。
なんとなく C/C++ のアドレスを取る & とアドレス変数の中身を操作するときの * に似ている感じがするのはわざとだろう。
しかし、だ。
cmd は &String ではなく String になっているのに * で参照を外し、続けて & で参照させている。
訳がわからない。
たぶん参考にしたのはこちらのサイトだろう。
「String は str への Deref を実装しているので」というのが理由らしい。
Deref だろう。
Deref<Target = str> を実装しているのが理由なのか str のメソッドも使えると書いてある。
& を付ければ &str としても扱えるとのこと。
なら、cmd を String ではなく &String にしたら &str 扱いになって大丈夫じゃないの?
が、それはダメだった。
“mismatched types expected reference &String found reference &'static str”
最初のエラーは “expected String, found &str” だったくせに、ここに来て &'static str とかいいやがった。
なら *cmd だけだと何型になるかというと、これは str 型だった。
しかしインスタンスとしては定義できないようでエラーになる。
サイズがコンパイル時に分からないらしい。
the size for values of type `str` cannot be known at compilation time
the trait `Sized` is not implemented for `str`
all local variables must have a statically known size
unsized locals are gated as an unstable feature
サイズが分からないというか、サイズを表す Sized が実装されていないから。
プリミティブ型の str がサイズを持たないということは、アドレス先頭を指しているだけ、みたいな状況だろうか。
とにかく str 単体で使うことはほとんどない、と書いてあった通りなんだろう。
String 型を &str 的に扱いたいなら &* をつける、と丸暗記でも良いのかもしれん。
おわりに
まだまだ自力では書けないなあ。