Derives and Traits¶
This page is a Reference for Incan derives, dunder overrides, and trait authoring. For guided learning, use:
Conflicts & precedence¶
Rules that resolve “derive vs dunder” (these are intentional and strict):
- Dunder overrides are explicit behavior: if you write a dunder (
__str__,__eq__,__lt__,__hash__), that is the behavior for that capability. - Conflicts are errors: you must not combine a dunder with the corresponding
@derive(...). - Auto-added traits are the exception: some traits are automatically added to
model/class/enum/newtype(see Automatic derives). You don’t need to spell them out.
Automatic derives¶
The compiler automatically adds these derives:
| Construct | Auto-added derives |
|---|---|
model |
Debug, Display, Clone |
class |
Debug, Display, Clone |
enum |
Debug, Display, Clone, Eq |
newtype |
Debug, Display, Clone (and Copy if underlying is Copy) |
Notes:
DebugandDisplayare always available for these constructs.Displayhas a default representation (like Python’s default__str__) unless you define__str__.
Derive catalog (quick index)¶
Only the items usable in @derive(...) are derives:
| Derive | Capability | Override | Notes |
|---|---|---|---|
| Debug | Debug formatting ({:?}) |
— | Auto-added |
| Display | Display formatting ({}) |
__str__ |
Auto-added |
| Eq | == / != |
__eq__ |
Conflicts are errors |
| Ord | Ordering + sorted(...) |
__lt__ |
Conflicts are errors |
| Hash | Set / Dict keys |
__hash__ |
Conflicts are errors |
| Clone | .clone() |
— | Auto-added |
| Copy | Implicit copy | — | Marker trait |
| Default | Type.default() |
— | Baseline constructor |
| Serialize | JSON stringify | — | json_stringify(value) |
| Deserialize | JSON parse | — | T.from_json(str) |
Detailed pages:
Derives:
Stdlib traits:
- Overview
- Collection protocols
- Indexing and slicing
- Callable objects
- Operator traits
- Conversion traits
Derive dependencies and requirements¶
Some derives imply prerequisite derives (the compiler adds them automatically):
| If you request | Compiler also adds |
|---|---|
Eq |
PartialEq |
Ord |
Eq, PartialEq, PartialOrd |
Semantic requirements (not “auto-added”):
- If you derive
Hash, you almost always also wantEq. Prefer@derive(Eq, Hash). - If you provide custom equality via
__eq__, your hashing (derived or custom) must remain consistent.
Common compiler diagnostics¶
Unknown derive¶
@derive(Debg) # Typo
model User:
name: str
Expected: “unknown derive” with a list of valid derive names.
Deriving a non-trait¶
model User:
name: str
@derive(User) # wrong: User is a model, not a derive/trait
model Admin:
level: int
Expected: “cannot derive a model/class” with a hint to use with TraitName for trait implementations.
Traits (authoring)¶
Traits define reusable capabilities. You opt in with with TraitName. Methods can be required (...) or have defaults.
trait Describable:
def describe(self) -> str:
return "An object"
class Product with Describable:
name: str
def main() -> None:
p = Product(name="Laptop")
println(p.describe())
@requires(...) (adopter contract)¶
@requires(...) is a decorator you can put on a trait to declare which adopter fields must exist (and what types
they must have).
Syntax:
@requires(field_a: TypeA, field_b: TypeB)
trait MyTrait:
...
What the compiler enforces:
- When a
class/modeladopts a trait (with MyTrait), it must provide all required fields with compatible types. - Trait default methods may access adopter fields like
self.fieldonly if that field is declared in@requires(...). - Mutating adopter fields still requires
mut self(same as normal methods).
Example:
@requires(name: str)
trait Loggable:
def log(self, msg: str) -> None:
println(f"[{self.name}] {msg}")
class Service with Loggable:
name: str
Example (mutation):
@requires(count: int)
trait Counter:
def bump(mut self) -> None:
self.count += 1
See also: Traits (authoring).
Debug (Automatic)¶
Format: {value:?} (Debug)
Override: not supported (Debug is compiler-generated).
model Point:
x: int
y: int
def main() -> None:
p = Point(x=10, y=20)
println(f"{p:?}") # Point { x: 10, y: 20 }
Display (Custom with __str__)¶
Format: {value} (Display)
Default behavior: custom types have a default Display representation.
Custom behavior: define __str__(self) -> str.
Conflict rule: if you define
__str__, you must not also@derive(Display).
model User:
name: str
email: str
def __str__(self) -> str:
return f"{self.name} <{self.email}>"
def main() -> None:
u = User(name="Alice", email="alice@example.com")
println(f"{u}") # Alice <alice@example.com>
println(f"{u:?}") # User { name: "Alice", email: "alice@example.com" }
Eq (Equality)¶
What it does: enables == / !=.
Custom behavior: define __eq__(self, other: Self) -> bool.
Conflict rule: if you define
__eq__, you must not also@derive(Eq).
model User:
id: int
name: str
def __eq__(self, other: User) -> bool:
return self.id == other.id
Ord (ordering)¶
What it does: enables ordering operators and sorted(...).
Custom behavior: define __lt__(self, other: Self) -> bool.
Conflict rule: if you define
__lt__, you must not also@derive(Ord).
model Task:
priority: int
name: str
def __lt__(self, other: Task) -> bool:
return self.priority < other.priority
Hash¶
What it does: enables use as Set members and Dict keys.
Custom behavior: define __hash__(self) -> int.
Conflict rule: if you define
__hash__, you must not also@derive(Hash).Consistency rule: if
a == b, thena.__hash__() == b.__hash__().
@derive(Eq, Hash)
model UserId:
id: int
Clone¶
What it does: enables .clone() (deep copy).
Auto-added for model/class/enum/newtype.
Copy¶
What it does: enables implicit copying (marker trait).
Use only for small value types; all fields must be Copy.
Default¶
What it does: provides Type.default() baseline construction.
Field defaults vs Default:
- Field defaults (
field: T = expr) are used by normal constructors when omitted. @derive(Default)addsType.default(); it uses field defaults when present, otherwise type defaults.
Constructor rule:
- If a field has no default, it must be provided when constructing the type.
@derive(Default)
model Settings:
theme: str = "dark"
font_size: int = 14
def main() -> None:
a = Settings() # OK: all omitted fields have defaults
b = Settings(font_size=16) # OK
c = Settings.default() # OK
Serialize¶
What it does: enables JSON serialization.
API:
json_stringify(value)→str
@derive(Serialize)
model User:
name: str
age: int
def main() -> None:
u = User(name="Alice", age=30)
println(json_stringify(u))
Deserialize¶
What it does: enables JSON parsing into a type.
API:
T.from_json(input: str)→Result[T, str]
@derive(Deserialize)
model User:
name: str
age: int
def main() -> None:
result: Result[User, str] = User.from_json("{\"name\":\"Alice\",\"age\":30}")
Compiler errors (reference)¶
Unknown derive (example)¶
@derive(Debg)
model User:
name: str
type error: Unknown derive 'Debg'
Deriving a non-trait (example)¶
model User:
name: str
@derive(User)
model Admin:
email: str
type error: Cannot derive 'User' - it is a model, not a trait
Reflection (automatic)¶
Models and classes provide:
__fields__() -> List[str]__class_name__() -> str
model User:
name: str
def main() -> None:
u = User(name="Alice")
println(u.__class_name__())
println(u.__fields__())