項目6:newtypeパターンを活用しよう
https://effective-rust.com/newtype.html
LT;DR
Newtype パターン を用いると以下が可能になる
同じ内部型を持つ型同士の区別
孤児ルール の回避
一方、Newtype パターンの型に対する操作を内部の型に 委譲 する手間もある
e.g. トレイト の実装 ...
hr.icon
型エイリアス は事実上単なるドキュメントに過ぎない
したがって、以下のコードでは PoundForceSeconds と NewtonSeconds を区別することができない
code:rs
pub type PoundForceSeconds = f64;
pub fn thruster_impulse(direction: Direction) -> PoundForceSeconds {
// ...
return 42.0;
}
pub type NewtonSeconds = f64;
pub fn update_trajectory(force: NewtonSeconds) {
// ...
}
そのため、NewtonSeconds を要求するところに PoundForceSeconds を渡しても、コンパイルエラーにならない
code:rs
let thruster_force: PoundForceSeconds = thruster_impulse(direction);
let new_direction = update_trajectory(thruster_force);
Newtype パターン を用いると、これらを区別することが可能
code:rs
pub struct PoundForceSeconds(f64);
pub fn thruster_impulse(direction: Direction) -> PoundForceSeconds {
// ...
return PoundForceSeconds(42.0);
}
pub struct NewtonSeconds(f64);
pub fn update_trajectory(force: NewtonSeconds) {
// ...
}
この状態で、NewtonSeconds を要求するところに PoundForceSeconds を渡すとコンパイルエラーになる
From を実装しておくと、簡単に変換することが可能
code:rs
impl From<PoundForceSeconds> for NewtonSeconds {
fn from(val: PoundForceSeconds) -> Self {
NewtonSeconds(4.44822 * val.0)
}
}
code:rs
let thruster_force: PoundForceSeconds = thruster_impulse(direction);
let new_direction = update_trajectory(thruster_force.into());
Newtype パターン は真偽値の曖昧さを解消する際にも利用できる
code:rs
struct DoubleSided(pub bool);
struct ColorOutput(pub bool);
fn print_page(sides: DoubleSided, color: ColorOutput) {
// ...
}
メモリ効率 や バイナリ 互換性が気になる場合、#[repr(transparent] 属性を付けることで、内部の型と同じメモリ表現になることを保証される
https://lo48576.gitlab.io/rust-custom-slice-book/defining-slice-types/repr-transparent.html
孤児ルール の回避
孤児ルール: ある クレート や型が実装できるのは、以下の条件のいずれかを満たす場合のみ
そのクレートそのトレイトを実装している
そのクレートでその型を定義している
そのため、以下のコードはコンパイルエラーとなる
code:rs
impl fmt::Display for rand::rngs::StdRng {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "<StdRng instance>")
}
}
オープンクラス のようなことはできない radish-miyazaki.icon
https://doc.rust-lang.org/stable/error_codes/E0117.html
存在理由: 曖昧さを回避するため
e.g.
依存グラフ にある 2 つのクレートが impl std::fmt:Display fot rand::rngs:StdRng を実装していた場合、コンパイラ や リンカ はどちらを選択したらよいか分からない
Newtype パターンを用いると、定義した新しい型はそのクレートで定義することになるため回避可能
code:rs
struct MyRng(rand::rngs::StdRng);
impl fmt::Display for MyRng {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "<MyRng instance>")
}
}
Newtype の制限
Newtype に対するすべての操作を、内部の型に 委譲 する必要がある
そのため、値を利用する際には .0 を付ける必要がある
またトレイト実装も Newtype 自身に対して行う必要がある
code:rs
#derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)
pub struct NewType(InnerType);
impl fmt::Display for NewType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目