Rustの練習
概要
完全に参照の部分に慣れていないので、これをどうやって対応したのかを自分の整理のためにもメモしていく
exerismでRustの勉強をしているが、その問題を使う
Simple Linked List
以下のような構造ときので後入れ先出しのパターンの場合
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
を上書きする。Option
のtake
が値を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
もそうだったけど、引数のmut
はtake
のために使う感じ
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))
}
Result
はOption
と同じようなmapping関数があるからそれを使うと良さそう。
Result
はErr
でも値を持てるからなんか今までの感覚と若干違うなぁ、悪くないなぁという印象。
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