項目6:newtypeパターンを活用しよう
LT;DR
同じ内部型を持つ型同士の区別
一方、Newtype パターンの型に対する操作を内部の型に 委譲 する必要がある 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);
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());
code:rs
struct DoubleSided(pub bool);
struct ColorOutput(pub bool);
fn print_page(sides: DoubleSided, color: ColorOutput) {
// ...
}
メモリ効率 や バイナリ 互換性が気になる場合、#[repr(transparent] 属性を付けることで、内部の型と同じメモリ表現になることを保証される 孤児ルール: ある クレート や型が実装できるのは、以下の条件のいずれかを満たす場合のみ そのクレートそのトレイトを実装している
そのクレートでその型を定義している
そのため、以下のコードはコンパイルエラーとなる
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 存在理由: 曖昧さを回避するため
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
pub struct NewType(InnerType);
impl fmt::Display for NewType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}