hiro99ma blog

Something technical

rust: Rustがんばろう 9日目

2025/02/22

はじめに

Rust を勉強することにした。
行き詰まる。

8章の宿題

8章のまとめ には課題というか宿題が書いてあった。

なかなか終わらない・・・。
特に時間がかかっているのは 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) で引数でとってきた &strString にしたりしたのだが、 ことごとく「一時的な値だ」ということで返せない。

たとえばこうすると “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 let binding 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]),
        }

cmdString だ。
なのに * を付けてさらに & まで付けている。

これを付けないと “add” と “print” の行で “expected String, found &str” というエラーになる。
文字列のプリミティブ型は &str なのでその主張は分かる。
では、と String::from().to_string() をここで使ってもエラーになる。 エラーの意味は分からんが、ここに書いたらいかんだろうなあという気持ちにはなるのでよいのだ。
ではこの文より前に let add = String::from("add"); みたいに変数を用意したいかというと、すれば解決はするのだがしたくない。
どうしたらいいんだ?と探した結果が &* だったのだ。

* は参照外し演算子(dereference operator)で、& の反対の意味を持つ。
なんとなく C/C++ のアドレスを取る & とアドレス変数の中身を操作するときの * に似ている感じがするのはわざとだろう。

しかし、だ。
cmd&String ではなく String になっているのに * で参照を外し、続けて & で参照させている。
訳がわからない。

たぶん参考にしたのはこちらのサイトだろう。

Stringstr への Deref を実装しているので」というのが理由らしい。
Deref だろう。
Deref<Target = str> を実装しているのが理由なのか str のメソッドも使えると書いてある。 & を付ければ &str としても扱えるとのこと。

なら、cmdString ではなく &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 的に扱いたいなら &* をつける、と丸暗記でも良いのかもしれん。

おわりに

まだまだ自力では書けないなあ。


 < Top page


コメント(Google Formへ飛びます)

GitHub

X/Twitter

Homepage