👻

Rustの練習

2021/05/01に公開

概要

完全に参照の部分に慣れていないので、これをどうやって対応したのかを自分の整理のためにもメモしていく

exerismでRustの勉強をしているが、その問題を使う

Simple Linked List

全容: https://exercism.io/tracks/rust/exercises/simple-linked-list/solutions/d0fdfb1c904344ecbf4bcf808c345cdc

以下のような構造ときので後入れ先出しのパターンの場合

pub struct SimpleLinkedList<T> {
    head: Option<Box<Node<T>>>
}

pub struct Node<T> {
    data: T,
    next: Option<Box<Node<T>>>
}

SimpleLinkedList#push

後入れ先出しのパターンの場合なので、headを上書きする

    pub fn push(&mut self, _element: T) {
        self.head = Some(Box::new(Node::new(_element, self.head.take())))
    }

mutで可変として、headを上書きする。Optiontakeが値をmoveする。
あれ?take()じゃなくてもいいじゃんって思うんだが、以下のように怒られる。

move occurs because `self.head` has type `std::option::Option<std::boxed::Box<Node<T>>>`, which does not implement the `Copy` trait

Nodeのcopyを作っていないから怒られるんだと思われ。
そして、takeの実装は以下の通りだが、おそらく、この場合新しくメモリの場所作ってmoveしてくれているんだろう的イメージ。

    pub fn take(&mut self) -> Option<T> {
        mem::take(self)
    }

SimpleLinkedList#pop

takeを使って移動させる。そして、その中身をmapからnextを入れる。
pushもそうだったけど、引数のmuttakeのために使う感じ

    pub fn pop(&mut self) -> Option<T> {
        self.head.take().map(|x| {
            self.head = x.next;
            x.data
        })
    }

SimpleLinkedList#peek

headの内容を取得する。popだと中身を移動させるがそうではない。

    pub fn peek(&self) -> Option<&T> {
        self.head.as_ref().map(|x| &x.data)
    }

そして、ややこしいのがas_ref()の順番で、以下の場合は

    pub fn peek(&self) -> Option<&T> {
        self.head.map(|x| &x.data)
    }

こんな感じで怒られる

cannot return reference to local data `x.data`

returns a reference to data owned by the current functionrustc(E0515)
lib.rs(56, 27): returns a reference to data owned by the current function

おそらく、self.head.map(|x| &x.data)の場合はmapに対して参照が渡らないのがまずい。
んじゃあ、以下ならどうなのか?

    pub fn peek(&self) -> Option<&T> {
        self.head.map(|x| x.data).as_ref()
    }

以下のように怒られる

cannot return value referencing temporary value

returns a value referencing data owned by the current functionrustc(E0515)
lib.rs(56, 9): returns a value referencing data owned by the current function
lib.rs(56, 9): temporary value created here

おそらく、cloneされたデータの参照になるのではないか?(いや、てかclone実装していないけど。。。?)

PaaS I/O

全容: https://exercism.io/tracks/rust/exercises/paasio/solutions/2d46e211c6804faa9aa498118a83cdaa

問題の内容がわからんかったから悩んだけど、回答を参照したら理解できた。
とりあえず、簡単に言うと、readとwriteするときの、総バイト数と実施数をカウントをして参照できるようなclassを作る課題になる。

bytes_through

横着して、保持しているdataをbyteにして読み込んでも良いんちゃうって思った。
こういう感じの場合に

pub struct ReadStats<R> {
    data: R
}

impl<R: Read> ReadStats<R> {
    pub fn bytes_through(&self) -> usize {
        self.data.bytes().count()
    }
}

ただ、この場合、以下のようにself.dataのデータがbyte呼び出し時にmoveされるっぽい

cannot move out of `self.data` which is behind a shared reference

move occurs because `self.data` has type `R`, which does not implement the `Copy` traitrustc(E0507)

ほんじゃ、Copy実装をしたRしか入らないのであれば良いのか?ってなるけど、テストコードで入ってくるRはstd::fs::Fileなのでそれを上書きするということとかをすることになるため、難しい。ちなみにジェネリック境界っていうっぽく、複数の場合は+で連結するようだ

impl<R: Read + Copy> ReadStats<R> {
    pub fn bytes_through(&self) -> usize {
        self.data.bytes().count()
    }
}

Nucleotide Count

全容: https://exercism.io/tracks/rust/exercises/nucleotide-count/solutions/94255b644a3c41cdb38dfb2c517120b6
これもそんなに難しい問題ではないが、もっとうまい感じにかけないかなぁと思いながら試行錯誤してた。

filter

まぁ、ネーミングはあれだなぁと思いながら。
Rustの場合Resultのときに?をつけていると、中身がErrなら早期リターンになる。
これを利用して、含まれていればokで、何もしないとかも行けるっぽい。乱用するとgotoっぽくて処理が追えなくなってしまう可能性もあるから、あくまでもverification的なのが良いかも。

fn filter(nucleotide: char) -> Result<(), char> {
    match nucleotide {
        n if NUCLEOTIDES.contains(&n) => Ok(()),
        _ => Err(nucleotide)
    }
}

pub fn count(nucleotide: char, dna: &str) -> Result<usize, char> {
    dna.chars()
        .try_for_each(filter)
        .and_then(|_| partition(dna).get(&nucleotide).ok_or(nucleotide).map(|&x| x))
}

ResultOptionと同じようなmapping関数があるからそれを使うと良さそう。
ResultErrでも値を持てるからなんか今までの感覚と若干違うなぁ、悪くないなぁという印象。

ETL

全容: https://exercism.io/tracks/rust/exercises/etl/solutions/9429754bbed841029f60422bdd15653d
key, valueのmapをvalue, keyのmapにしたい的な話なのでそんなに難しくはなかった。
ただ、一旦closureの外の変数を呼ぶ場合のところだけ。
valueの中身がVec<char>なのでそれを回しながら、tappleにしたいので、以下のようにやってみたときに
.flat_map(|(k, v)| v.iter().map(|x| (x.to_ascii_lowercase(), *k)))
以下のように怒られる。なるほど、ってかんじ。このあたりはJavaとかならfinalの変数しか呼べないとかだったはず(最近はclosure内で代入してなければ、コンパイルが通る的な感じのハズ)

closure may outlive the current function, but it borrows `k`, which is owned by the current function

may outlive borrowed value `k`

help: to force the closure to take ownership of `k` (and any other referenced variables), use the `move` keyword: `move |x|`

Discussion