前の記事
- 【0】 準備 ← 初回
- ...
- 【10】 トレイト境界・文字列・Derefトレイト ~トレイトのアレコレ~ ← 前回
- 【11】 Sized トレイト・From トレイト・関連型 ~おもしろトレイトと関連型~ ← 今回
全記事一覧
- 【0】 準備
- 【1】 構文・整数・変数
- 【2】 if・パニック・演習
- 【3】 可変・ループ・オーバーフロー
- 【4】 キャスト・構造体 (たまにUFCS)
- 【5】 バリデーション・モジュールの公開範囲 ~ → カプセル化!~
- 【6】 カプセル化の続きと所有権とセッター ~そして不変参照と可変参照!~
- 【7】 スタック・ヒープと参照のサイズ ~メモリの話~
- 【8】 デストラクタ(変数の終わり)・トレイト ~終わりと始まり~
- 【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ~Empowerment 💪 ~
- 【10】 トレイト境界・文字列・Derefトレイト ~トレイトのアレコレ~
- 【11】 Sized トレイト・From トレイト・関連型 ~おもしろトレイトと関連型~
- 【12】 Clone・Copy・Dropトレイト ~覚えるべき主要トレイトたち~
- 【13】 トレイトまとめ・列挙型・match式 ~最強のトレイトの次は、最強の列挙型~
- 【14】 フィールド付き列挙型とOption型 ~チョクワガタ~
- 【15】 Result型 ~Rust流エラーハンドリング術~
- 【16】 Errorトレイトと外部クレート ~依存はCargo.tomlに全部お任せ!~
- 【17】 thiserror・TryFrom ~トレイトもResultも自由自在!~
- 【18】 Errorのネスト・慣例的な書き方 ~Rustらしさの目醒め~
- 【19】 配列・動的配列 ~スタックが使われる配列と、ヒープに保存できる動的配列~
- 【20】 動的配列のリサイズ・イテレータ ~またまたトレイト登場!~
- 【21】 イテレータ・ライフタイム ~ライフタイム注釈ようやく登場!~
- 【22】 コンビネータ・RPIT ~ 「
Iterator
トレイトを実装してるやつ」~ - 【23】
impl Trait
・スライス ~配列の欠片~ - 【24】 可変スライス・下書き構造体 ~構造体で状態表現~
- 【25】 インデックス・可変インデックス ~インデックスもトレイト!~
- 【26】 HashMap・順序・BTreeMap ~Rustの辞書型~
- 【27】 スレッド・'staticライフタイム ~並列処理に見るRustの恩恵~
- 【28】 リーク・スコープ付きスレッド ~ライフタイムに技あり!~
- 【29】 チャネル・参照の内部可変性 ~Rustの虎の子、mpscと
Rc<RefCell<T>>
~ - 【30】 双方向通信・リファクタリング ~返信用封筒を入れよう!~
- 【31】 上限付きチャネル・PATCH機能 ~パンクしないように制御!~
- 【32】
Send
・排他的ロック(Mutex
)・非対称排他的ロック(RwLock
) ~真打Arc<Mutex<T>>
登場~ - 【33】 チャネルなしで実装・Syncの話 ~考察回です~
- 【34】
async fn
・非同期タスク生成 ~Rustの非同期入門~ - 【35】 非同期ランタイム・Futureトレイト ~非同期のお作法~
- 【36】 ブロッキング・非同期用の実装・キャンセル ~ラストスパート!~
- 【37】 Axumでクラサバ! ~最終回~
- 【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】
- 【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か
100 Exercise To Learn Rust 演習第11回になります!今回は演習中心に軽めに済ませたかったのですが、まぁそうも言ってられなさそうですね。無理せず行きます!
今回の関連ページ
[04_traits/08_sized] Sized トレイト
問題はこちらです。今回はたまにある「コンパイルエラーが出ることを確かめたら次のエクササイズに進んでいいよ」問題のようです。
pub fn example() {
// Trying to get the size of a str (or any other DST)
// via `std::mem::size_of` will result in a compile-time error.
//
// TODO: Comment out the following line and move on to the next exercise.
std::mem::size_of::<str>();
}
解説
「 &str
や &[T]
は見るのに str
や [T]
を直接扱っているものは見ないな?」という疑問に関するアンサーが今回のBookの内容です。
Rustでは、「コンパイル時にメモリ上のサイズが決まるもの」に Sized
という マーカートレイト を、逆に「コンパイル時にメモリ上のサイズ不定なもの(Dynamically Sized Type, DST)」に !Sized
というマーカートレイトを付けます。
「マーカートレイト」というのは、特定のメソッド等は持たないものの、 対象の型の性質を表すのに使うトレイト です。この他にも Copy
、 Sync
、 Send
等々色々あります。
DSTな、つまり !Sized
な型には str
や [T]
の他に、 トレイトオブジェクト dyn Trait
(第18回等で取り扱い)があります。彼らはコンパイル時サイズ不定なので、 size_of
関数のジェネリクスに渡すと(func::<T>()
みたいにturbofish ::<>
を使って渡しています。)エラーになってしまいます。ちなみに size_of
に限らず全ての関数はデフォルトでジェネリクス型に Sized
を要求してきます。 サイズ不定なやつを渡したい場合 ?Sized
をトレイト境界に書く必要があります。
問題に戻りましょう。せっかくなのでコンパイルが通る例を記載しておきます。
pub fn example() {
// std::mem::size_of::<str>();
// std::mem::size_of::<[usize]>();
// 次はコンパイルが通る
std::mem::size_of::<&str>();
std::mem::size_of::<&[usize]>();
std::mem::size_of::<[usize; 3]>();
}
DST自体は ?Sized
でも、 DSTの参照は Sized
で、その大きさは普通の参照なら usize
と同様(64bit OSなら8バイト)になっています。なので上記コードはコンパイルが通ります。
[04_traits/09_from] From トレイト
問題はこちらです。第9回のOrphan ruleにてコンパイルを通すために使用したニュータイプパターン(ラッパー)が本問題でも使われていますね。
// TODO: Implement the `From` trait for the `WrappingU32` type to make `example` compile.
pub struct WrappingU32 {
value: u32,
}
fn example() {
let wrapping: WrappingU32 = 42.into();
let wrapping = WrappingU32::from(42);
}
u32
-> WrappingU32
というキャストを手軽に行えるようにするために、 impl From<u32> for WrappingU32
してほしいということらしいです。
Rustではキャストもトレイトで実現されている1わけです!
解説
実装すればよいのです。
impl From<u32> for WrappingU32 {
fn from(value: u32) -> Self {
Self { value }
}
}
ちなみに impl From<T> for U
の方を実装すると impl Into<U> for T
も自動実装されます。こういう関係にあるものをDual Trait (双子トレイト...?) というらしい、便利ですね。
Bookに書いてあるように、Dual Traitの実装自体は難しくないので、試しに何か書いてみると面白いかもしれません。
trait MyFrom<T> {
fn my_from(value: T) -> Self;
}
trait MyInto<U> {
fn my_into(self) -> U;
}
// Dual Traitたらしめている部分
impl<T, U> MyInto<U> for T
where
U: MyFrom<T>
{
fn my_into(self) -> U {
U::my_from(self)
}
}
// 意味としては、「型Tに対して、MyFrom<T>を実装しているUについてMyInto<U>を実装する」
[04_traits/10_assoc_vs_generic]
問題はこちらです。なんかBookにめっちゃ良いこと書いてるのにそっちをあんまり意識してない問題...?!
// TODO: Define a new trait, `Power`, that has a method `power` that raises `self`
// to the power of `n`.
// The trait definition and its implementations should be enough to get
// the tests to compile and pass.
//
// Recommendation: you may be tempted to write a generic implementation to handle
// all cases at once. However, this is fairly complicated and requires the use of
// additional crates (i.e. `num-traits`).
// Even then, it might be preferable to use a simple macro instead to avoid
// the complexity of a highly generic implementation. Check out the
// "Little book of Rust macros" (https://veykril.github.io/tlborm/) if you're
// interested in learning more about it.
// You don't have to though: it's perfectly okay to write three separate
// implementations manually. Venture further only if you're curious.
#[cfg(test)]
mod tests {
use super::Power;
#[test]
fn test_power_u16() {
let x: u32 = 2_u32.power(3u16);
assert_eq!(x, 8);
}
#[test]
fn test_power_u32() {
let x: u32 = 2_u32.power(3u32);
assert_eq!(x, 8);
}
#[test]
fn test_power_ref_u32() {
let x: u32 = 2_u32.power(&3u32);
assert_eq!(x, 8);
}
}
累乗のトレイト Power
💪 を実装してほしいという問題ですね。 num-traits
やマクロを使ったほうが良さそうというアドバイス付きです。
解説
というわけで num::Num
トレイトとマクロ両方を使って実装してみました。
use num::Num;
use std::collections::HashMap;
use std::hash::Hash;
fn power_inner<N>(n: N, e: u32) -> N
where
N: Num + Hash + Eq + Clone + Copy + std::fmt::Debug,
{
if e == 0 {
return N::one();
}
if e == 1 {
return n;
}
let vv = power_inner(n, e / 2);
let v = vv * vv * if e % 2 == 0 { N::one() } else { n };
v
}
trait Power<RHS = Self>: Num + Hash + Eq + Clone + Copy {
fn power(self, rhs: RHS) -> Self;
}
macro_rules! power_impl {
( $(($t:ty, $r:ty))* ) => {$(
impl Power<$r> for $t {
fn power(self, rhs: $r) -> Self {
power_inner(self, rhs.clone().into())
}
}
)*}
}
power_impl!((u32, u32)(u32, u16)(u32, &u32)(u128, u32));
マクロを使う場合トレイト側のトレイト境界は不要ですが、試行錯誤の結果残していました。まぁあるとマクロのシグネチャを見た人に親切かなと思い残しています。
演習問題とは関係なく、Bookの方では「ジェネリクス」と「関連型」どちらを使うべき?という話題が繰り広げられています。本記事でもその指針に触れたら今回を終わりたいと思います。
Conclusion
To recap:
- Use an associated type when the type must be uniquely determined for a given trait implementation.
- Use a generic parameter when you want to allow multiple implementations of the trait for the same type, with different input types.
とあります。筆者は以下のように捉えています。
- トレイトの自身の型以外の型指定は引数のような身分: ジェネリクスを使うべきシーン。
impl Power<U> for T
は、T × U -> 実装
という写像と捉えられる。 - トレイトの自身の型以外の型は、トレイトの実装により決まる、つまり返り値のような身分: 関連型を使うべきシーン。
impl Deref for T
とした時、 型Target = U
が一意に定まるべき
ジェネリクスと関連型は別に混在していてもよくて、その場合は上記の指針で考えれば良いんじゃないかなと思っています。
では次の問題に行きましょう!
次の記事: 【12】 Clone・Copy・Dropトレイト ~覚えるべき主要トレイトたち~
-
もちろんですが、
as
を使用したプリミティブ型のキャストのことではなく、このキャストにはトレイトが関わっていません(確か)。しかし使用頻度はfrom/into
またはtry_from/try_into
の方が多いんじゃないかなと思います。 ↩