Welcome - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/
ここまでRustをなんとなく書いてきたけど、今がそういう時期かなと思ったので。
まあ全部ではなく、気になるところやあまり自信のなかったところを中心にやってみた。
overflow-check
Overflow and underflow - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/02_basic_calculator/08_overflow.html#overflow-check
前にハマったやつなので覚えてた。
Rustの整数オーバーフローは、デフォルトではdebugビルドでのみパニックする | Memory ice cubes https://leaysgur.github.io/posts/2024/08/28/133918/
Operator traits
Operator overloading - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/04_traits/03_operator_overloading.html
(Partial)Eq
、(Partial)Ord
、std::ops::Add
とかstd::opts::Sub
とかあのあたり。
それぞれ==
や<
などのオペレーターに対応してて、実装してあれば使える。というか本当にどこもかしこもtrait
で成り立ってるな。
何気なく使ってたassert_eq!()
も、内部的にはleft == right
してるだけで、そのためにはEq
が必要というのも納得。
あとDerive macroの便利さを改めて実感した。フィールドの多いstruct
でいちいち実装してらんないもんね。
Trait bounds
Trait bounds - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/04_traits/05_trait_bounds.html#trait-bounds-1
個人的な鬼門だったやつ。
この2つが同義ってことにも最初は馴染めなかったけど、やっとわかってきた気がする。
fn print_if_even<T>(n: T) where T: IsEven + Debug {}
fn print_if_even<T: IsEven + Debug>(n: T) {}
もう常にwhere
で書いてくれ・・・!って気持ちになってた。
From
とInto
From trait - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/04_traits/09_from.html#into
From
があればinto()
できる!
as
を書く前にまずinto()
してみる!
まあそれでも、純粋に型変換をやる関数を用意するべきか、From
でやるべきかは永遠にわからない。
ちなみに、TryFrom
とTryInto
も、どっちかがあればどっちもできるようになる、Dual traitってやつらしい。
Generic trait
Associated vs generic types - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/04_traits/10_assoc_vs_generic.html#output
これも学びだった。
impl Add<&u32> for &u32 {
type Output = u32; // 👀
fn add(self, rhs: &u32) -> Self::Output { // 👀
}
}
なぜこの実装で、-> Self
ではなく、type Output
を経由するのか。
このtrait
を-> Self
で定義しちゃうと、結果も&u32
になってしまい、&1 + &2
の結果まで参照の&u32
になってしまう。それでいい場合もあるかもしれないが、実体のu32
のほうが取り回しやすいのは間違いない。
あとは、このクッションがあるおかげで、まったく関係ない異なる型を返したいときにも使える、と。
ちなみに、こう書いてもコンパイラに怒られないのは、元々のtrait
ではSelf::Output
を返すように定義されていて、たまたま直接書いてる-> u32
と型が一致してるから。
もちろん-> 他の型
にしちゃうとエラーになる。
impl Add<&u32> for &u32 {
type Output = u32;
fn add(self, rhs: &u32) -> u32 { // 👀
}
}
なるほどな。
Drop trait
Drop trait - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/04_traits/13_drop.html
JS/TSでいう[Symbol.dispose]
ね!
そしてDrop
とCopy
は両立できない、と。
Lifetime elision
Lifetimes - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/06_ticket_management/06_lifetimes#lifetime-elision
'_
でライフタイムの明示を省略できるやつ。
今まではコンパイラに「そこは明示しなくていいよ」って言われたときだけ、'_
を書いてた。
なので、どういう時に書ける?とかはあまり考えてこなかった。
impl<'_> IntoIterator for &'_ TicketStore { // ただ型を書くところなので、場所としてはOK(中で使えないから結局NGだが)
// ここは型それ自体の定義なのでNG
type Item = &'_ Ticket;
type IntoIter = std::slice::Iter<'_, Ticket>;
}
型定義時には使えなくて、型を利用する時は使える。 こんな初歩的なことがわかっていなかったとは・・・ってなった。
RPIT
impl Trait - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/06_ticket_management/08_impl_trait#rpit
-> impl Trait
のこと、“Return Position Impl Trait”っていうらしい。
RPIT lifetime capture rules - The Rust Edition Guide https://doc.rust-lang.org/edition-guide/rust-2024/rpit-lifetime-capture.html
そして引数版の、“Argument Position Impl Trait”ってのもあるらしい。
そして引数の場合は、APITよりも、ジェネリックな型パラメーターを使う方がおすすめとのこと。
// これよりも
pub fn add_ticket(&mut self, ticket: impl Into<Ticket>) { }
// こっちがおすすめ
pub fn add_ticket<T>(&mut self, ticket: T) where T: Into<Ticket> { }
これは、呼び出し側がadd_ticket::<>()
で好きな型を指定できるかららしい。
この::<>
という書き方は、魚っぽいからTurbofish syntaxというらしい。想像力があるな!
impl<K, V> HashMap<K, V> where K: Eq + Hash
HashMap - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/06_ticket_management/15_hashmap.html#key-requirements
何気なく使ってたHashMap
も、trait
で成り立ってるんだなということを改めて実感した。
言われてみれば実装としても、キーがHash
でEq
(そしてPartialEq
)できないと比較できないもんね。
やはりtrait
はRustの核なのだなあ。
Threads ‘static
‘static lifetime - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/07_threads/02_static#static-lifetime
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static
{ }
Send
じゃないとスレッドをまたがって共有できない'static
じゃないものは、いつ終わるかわからないスレッドとしては認めるわけにはいかないmove
するか、ローカルで定義するなど
trait
を改めて理解した今なら、この定義がスッと読める!これは嬉しい。
Futures
Runtime - 100 Exercises To Learn Rust https://rust-exercises.com/100-exercises/08_futures/03_runtime.html
この章も学びは多かったけど、結局tokio
の使い方という話になるのだろうか。
Rustとしてasync / .await
のシンタックスは存在するけど、そのランタイムは実装されてないのは驚きだった。
用途に応じてタスクをどう捌くか?を委ねられるというのは、いろんなシーンで使われる言語としては妥当なのか・・・な?
Future
になった非同期関数は、.await
するかそれ相当の処理でpoll
されるまで実行されないってのは、JSのawait
とは違って慣れるのが大変そう。
まだこのあたりは仕様もまだ完全ではないらしいので、またいずれおさらいする予定で。