Newtypes
Nesta página
Um newtype cria um tipo distinto a partir de outro, sem custo em tempo
de execução. O compilador recusa misturar UserId(int) com OrderId(int)
mesmo que ambos sejam int por baixo — o tipo é o contrato.
IDs distintos, Email com helpers, unidades de medida e fábrica com validação.
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
Boas práticas:
- Crie o valor com
Tipo.new(v)e extraia com.unwrap(). - Para adicionar comportamento, escreva funções de topo que recebem o
newtype —
fn email_local(e: Email) -> str { ... }— em vez de depender deimplsobre newtype, que não é estável. - Use newtypes para IDs, unidades (Metros, Pés, Graus) e qualquer valor primitivo que o domínio proíbe de trocar acidentalmente.