Prompt
非負整数しか許さないばあいにuintを型に使うのはまだ不適切になるのか?
非負整数における uint (u32/u64) の是非
「非負整数を表現するのに uint (あるいは Rust の u32 )を使うのは、型として適切か?」
結論から言うと、「計算機科学的には正しいが、ドメイン駆動(DDD)やビジネスロジックの観点では、まだ不十分(不適切)とされることが多い」 んだ。
なぜ uint だけでは足りないのか?
-
アンダーフローの罠:
Rust の
u32で0 - 1を行うと(デバッグビルドなら)パニックする。型が「非負」を保証していても、演算の結果として「負」になろうとする瞬間、プログラムが死ぬ。これは「安全」ではあるけれど、ビジネスロジックとしては「不完全」だ。 -
意味の広さ(ドメインの不一致):
u32は 0 から 約42億までの値を許容するよね。でも、例えば「ユーザーの年齢」なら 0〜150 程度でいいはずだ。u32を使うということは、暗黙的に「42億歳まで許容する」というドキュメントを公開しているのと同じになってしまう。
「真にうるさい」者の最適解:Newtype + バリデーション
君の「型をドキュメントにする」という美学を貫くなら、u32 を直接使うのではなく、「意味を持った型」 に昇華させるのが正解だ。
Rust での実装例
Rust
/// 非負整数(例えば年齢)を表現する型。
/// u32 を直接使うのではなく、コンストラクタで不変条件を強制する。
pub struct Age(u32);
impl Age {
pub fn new(value: u32) -> Result<Self, String> {
if value > 150 {
return Err("人間にしては長生きすぎます".into());
}
Ok(Self(value))
}
}
-
メリット: この
Age型を受け取る関数を書けば、文章で「0から150までですよ」と注釈を書く必要がない。「Age型が存在していること自体が、バリデーション済みである証明」 になるからだ。 -
型安全の極致: これを 「 Parse, don’t validate 」 と呼ぶよ。データをただチェックするのではなく、正しい型に変換(パース)してしまう考え方だ。
豆知識:符号付き整数をあえて使う派閥
実は、GoやGoogleのエンジニアリングガイドでは、たとえ非負整数であっても int (符号付き)を使うことが推奨される場合がある。理由は「引き算をした時に負になってもパニックせず、エラー処理( if val < 0 )に回せるから」という実利的な判断だ。
でも、君のような「型で語る」潔癖派にとっては、これは少し「負けた」感じがするかもしれないね(笑)。
次のステップ
次は、「TS で Branded Types を使って、 NonNegativeInteger を擬似的に表現する方法」 について深掘りしてみる?
Prompt
確かに、uintを使ってもまだ整数が負にならないという証明ができているわけではない。これはRustでさえも半分そうだね。
その通り!uint(u32など)を使っても、「演算の結果が範囲内に収まること」までは型システムが静的に保証しきれない。結局、実行時のパニックやチェックが必要になるから、本当の意味での「証明」には一歩届かないんだよね。