Newtype
場合によっては、ある型を別の型と同じように振る舞わせたい、または 型エイリアスだけでは不十分なときに、コンパイル時に何らかの振る舞いを 強制したいことがあります。
たとえば、セキュリティ上の考慮事項(例: パスワード)により、String に対してカスタムの Display 実装を作成したい場合です。
このような場合、Newtype パターンを使用して 型安全性 と
カプセル化 を提供できます。
説明
単一のフィールドを持つタプル構造体を使用して、ある型の不透明なラッパーを作成します。
これにより、型のエイリアス(type アイテム)ではなく、新しい型が作成されます。
例
use std::fmt::Display;
// String の Display トレイトをオーバーライドするために Newtype Password を作成する
struct Password(String);
impl Display for Password {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "****************")
}
}
fn main() {
let unsecured_password: String = "ThisIsMyPassword".to_string();
let secured_password: Password = Password(unsecured_password.clone());
println!("unsecured_password: {unsecured_password}");
println!("secured_password: {secured_password}");
}
unsecured_password: ThisIsMyPassword
secured_password: ****************
動機
newtype の主な動機は抽象化です。型どうしで実装の詳細を共有しつつ、インターフェイスを正確に制御できます。 API の一部として実装型を公開するのではなく newtype を使用することで、後方互換性を保ちながら実装を変更できます。
newtype は単位を区別するために使用できます。たとえば、f64 をラップして、区別可能な Miles と Kilometres を与えることができます。
利点
ラップされた型とラッパー型には型の互換性がありません(type を使用する場合とは異なります)。そのため、newtype のユーザーがラップされた型とラッパー型を「混同」することは決してありません。
newtype はゼロコスト抽象化であり、実行時のオーバーヘッドはありません。
プライバシーシステムにより、ユーザーはラップされた型にアクセスできません(フィールドが private である場合。これはデフォルトです)。
欠点
newtype の欠点(特に型エイリアスと比較した場合)は、特別な言語サポートがないことです。これは、大量の ボイラープレートが発生する可能性があることを意味します。 ラップされた型で公開したい各メソッドに対して「パススルー」メソッドが必要であり、ラッパー型にも実装したい各トレイトに対して impl が必要です。
議論
newtype は Rust コードで非常によく使われます。抽象化や単位の表現が最も一般的な用途ですが、他の理由でも使用できます。
- 機能を制限する(公開される関数や実装されるトレイトを減らす)、
- コピーセマンティクスを持つ型にムーブセマンティクスを持たせる、
- より具体的な型を提供し、それによって内部型を隠すことによる抽象化。 例:
pub struct Foo(Bar<T1, T2>);
ここで、Bar は何らかの公開されたジェネリック型であり、T1 と T2 は何らかの内部型である可能性があります。
私たちのモジュールのユーザーは、Foo を Bar を使用して実装していることを知るべきではありませんが、ここで実際に隠しているのは T1 と T2 という型と、それらが Bar とともにどのように使用されるかです。
関連項目
- 本の Advanced Types
- Haskell の Newtypes
- 型エイリアス
- derive_more。newtype 上で多くの 組み込みトレイトを derive するためのクレートです。
- The Newtype Pattern In Rust