Newtypes
En esta página
Un newtype crea un tipo distinto a partir de otro, sin costo en tiempo
de ejecución. El compilador rechaza mezclar UserId(int) con OrderId(int)
aunque ambos sean int por debajo — el tipo es el contrato.
IDs distintos, Email con helpers, unidades de medida y fábrica con validación.
11-newtypes-rich.zolo
// Feature: "Rich" newtypes — opaque wrappers with helpers
// Syntax: `newtype X(T)`; create with `X.new(v)`; extract with `.unwrap()`;
// add behavior via top-level functions that take the
// newtype.
// When to use: strong domain types (Email, UserId, Money) that
// the typechecker refuses to mix with the underlying type.
use std::String
newtype UserId(int)
newtype OrderId(int)
// Distinct IDs: typechecker refuses to swap the order.
fn buy(user: UserId, order: OrderId) {
print("user {user.unwrap()} bought order {order.unwrap()}")
}
let u = UserId.new(7)
let o = OrderId.new(99)
buy(u, o)
// buy(o, u) // ERROR at compile-time: incompatible types
// Newtype with helpers — top-level functions that take the newtype.
newtype Email(str)
fn email_local(e: Email) -> str {
let parts = e.unwrap().split("@")
return parts[0]
}
fn email_domain(e: Email) -> str {
let parts = e.unwrap().split("@")
return parts[1]
}
let e = Email.new("alice@zolo.dev")
print(e.unwrap()) // alice@zolo.dev
print(email_local(e)) // alice
print(email_domain(e)) // zolo.dev
// Newtype for "units" — distinguishes Meters vs Feet.
newtype Meters(float)
newtype Feet(float)
fn meters_to_feet(m: Meters) -> Feet {
return Feet.new(m.unwrap() * 3.28084)
}
fn feet_to_meters(f: Feet) -> Meters {
return Meters.new(f.unwrap() / 3.28084)
}
let m = Meters.new(100.0)
let f = meters_to_feet(m)
print(f.unwrap()) // ~328.084
let back = feet_to_meters(f)
print(back.unwrap()) // ~100.0
// Factory with light validation — enforces an invariant.
fn make_user_id(n: int) -> UserId {
if n < 0 {
return UserId.new(0)
}
return UserId.new(n)
}
let safe = make_user_id(-3)
print(safe.unwrap()) // 0
let ok = make_user_id(42)
print(ok.unwrap()) // 42
Buenas prácticas:
- Crea el valor con
Tipo.new(v)y extráelo con.unwrap(). - Para añadir comportamiento, escribe funciones de nivel superior que
reciban el newtype —
fn email_local(e: Email) -> str { ... }— en lugar de depender deimplsobre el newtype, que no es estable. - Usa newtypes para IDs, unidades (Metros, Pies, Grados) y cualquier valor primitivo que el dominio prohíba intercambiar accidentalmente.