Proposal: Derived Traits
Status: Approved (partially superseded) Author: Eric (with AI assistance) Created: 2026-01-30 Approved: 2026-01-30 Affects: Compiler, type system, traits
Errata (added 2026-02-20)
Partially superseded by capability-unification-generics-proposal: The
#derive(Trait)syntax described in this proposal is replaced bytype T: Trait = { ... }syntax (:trait clause on type declarations). Derivation rules, field constraints, error codes, and the set of 7 derivable traits remain valid. Only the syntax for declaring derivation changes.
Summary
This proposal formalizes the #derive attribute semantics, including derivable traits, derivation rules, field constraints, and error handling.
Problem Statement
The spec mentions #derive(Eq, Hashable, Clone) syntax but leaves unclear:
- Complete list: Which traits are derivable?
- Field requirements: What constraints apply to fields?
- Derivation rules: How is each trait derived?
- Ordering: Does derive order matter?
- Errors: What happens when derivation fails?
Syntax
#derive(Trait1, Trait2, ...)
type TypeName = { ... }
Multiple traits can be derived in a single attribute. The attribute must immediately precede the type definition.
Derivable Traits
Core Traits
| Trait | Requirement | Generated Implementation |
|---|---|---|
Eq | All fields implement Eq | Field-wise equality |
Hashable | All fields implement Hashable | Combined field hashes |
Comparable | All fields implement Comparable | Lexicographic comparison |
Clone | All fields implement Clone | Field-wise clone |
Default | All fields implement Default | Field-wise default |
Debug | All fields implement Debug | Formatted struct representation |
Printable | All fields implement Printable | Human-readable representation |
Derivation Rules
Eq
Field-wise equality comparison:
#derive(Eq)
type Point = { x: int, y: int }
// Generated:
impl Point: Eq {
@equals (self, other: Point) -> bool =
self.x == other.x && self.y == other.y
}
For sum types:
#derive(Eq)
type Status = Pending | Running(progress: int) | Done
// Generated:
impl Status: Eq {
@equals (self, other: Status) -> bool = match (self, other) {
(Pending, Pending) -> true
(Running(a), Running(b)) -> a == b
(Done, Done) -> true
_ -> false
}
}
Hashable
Combined hash from all fields:
#derive(Hashable)
type Point = { x: int, y: int }
// Generated:
impl Point: Hashable {
@hash (self) -> int = {
let h = 0
h = hash_combine(seed: h, value: self.x.hash())
h = hash_combine(seed: h, value: self.y.hash())
h
}
}
Invariant: If a == b, then a.hash() == b.hash(). Deriving Hashable without Eq is a warning.
Comparable
Lexicographic field comparison (declaration order):
#derive(Comparable)
type Point = { x: int, y: int }
// Generated:
impl Point: Comparable {
@compare (self, other: Point) -> Ordering = match compare(left: self.x, right: other.x) {
Equal -> compare(left: self.y, right: other.y)
result -> result
}
}
For sum types, variants compare by declaration order:
#derive(Comparable)
type Priority = Low | Medium | High
// Low < Medium < High (declaration order)
Clone
Field-wise cloning:
#derive(Clone)
type Config = { host: str, port: int, options: Options }
// Generated:
impl Config: Clone {
@clone (self) -> Config = Config {
host: self.host.clone(),
port: self.port.clone(),
options: self.options.clone(),
}
}
Default
Field-wise default construction:
#derive(Default)
type Config = { host: str, port: int, debug: bool }
// Generated:
impl Config: Default {
@default () -> Config = Config {
host: str.default(), // ""
port: int.default(), // 0
debug: bool.default(), // false
}
}
Sum types cannot derive Default (which variant?).
Debug
Structural representation with type name:
#derive(Debug)
type Point = { x: int, y: int }
// Generated:
impl Point: Debug {
@debug (self) -> str = `Point \{ x: {self.x.debug()}, y: {self.y.debug()} \}`
}
Point { x: 1, y: 2 }.debug() // "Point { x: 1, y: 2 }"
Printable
Human-readable representation with type name:
#derive(Printable)
type Point = { x: int, y: int }
// Generated:
impl Point: Printable {
@to_str (self) -> str = `Point({self.x}, {self.y})`
}
Point { x: 1, y: 2 }.to_str() // "Point(1, 2)"
Note: Types implementing Printable automatically implement Formattable via a blanket implementation. Deriving Printable therefore provides Formattable as well.
Field Constraints
All Fields Must Implement
Derivation fails if any field doesn’t implement the required trait:
type NonHashable = { data: FileHandle } // FileHandle: !Hashable
#derive(Hashable)
type Container = { item: NonHashable } // ERROR
Recursive Types
Recursive types can derive traits:
#derive(Eq, Clone, Debug)
type Tree = Leaf(value: int) | Node(left: Tree, right: Tree)
The generated implementation handles recursion correctly.
Multiple Derives
Single Attribute
#derive(Eq, Hashable, Clone, Debug)
type Point = { x: int, y: int }
Multiple Attributes
#derive(Eq, Hashable)
#derive(Clone, Debug)
type Point = { x: int, y: int }
Both forms are equivalent.
Order Independence
Derive order does not affect behavior:
#derive(Hashable, Eq) // Same as #derive(Eq, Hashable)
Generic Types
Conditional Derivation
Generic types derive traits when type parameters satisfy constraints:
#derive(Eq, Clone, Debug)
type Pair<T> = { first: T, second: T }
// Pair<int> implements Eq, Clone, Debug (int implements all)
// Pair<FileHandle> implements Debug only (FileHandle: !Eq, !Clone)
The compiler generates bounded implementations:
impl<T: Eq> Eq for Pair<T> { ... }
impl<T: Clone> Clone for Pair<T> { ... }
impl<T: Debug> Debug for Pair<T> { ... }
Cannot Derive
Traits Not Derivable
Some traits cannot be derived:
| Trait | Reason |
|---|---|
Iterator | Requires custom next logic |
Iterable | Requires custom iter logic |
Into | Requires custom conversion logic |
Drop | Requires custom cleanup logic |
Sendable | Automatically derived by compiler |
Manual Implementation Required
// Iterator cannot be derived
impl MyIter: Iterator {
type Item = int
@next (self) -> (Option<int>, Self) = ... // Custom logic
}
Error Messages
Field Missing Trait
error[E0880]: cannot derive `Eq` for `Container`
--> src/types.ori:2:10
|
2 | #derive(Eq)
| ^^ `Eq` cannot be derived
3 | type Container = { item: FileHandle }
| ---------- `FileHandle` does not implement `Eq`
|
= help: implement `Eq` manually or use a different field type
Non-Derivable Trait
error[E0881]: trait `Iterator` cannot be derived
--> src/types.ori:1:10
|
1 | #derive(Iterator)
| ^^^^^^^^ not derivable
|
= note: derivable traits: Eq, Hashable, Comparable, Clone, Default, Debug, Printable
= help: implement `Iterator` manually
Default for Sum Type
error[E0882]: cannot derive `Default` for sum type
--> src/types.ori:1:10
|
1 | #derive(Default)
| ^^^^^^^ not derivable for sum types
2 | type Status = Pending | Running | Done
|
= note: sum types have multiple variants; no unambiguous default
= help: implement `Default` manually to specify which variant
Hashable Without Eq Warning
warning[W0100]: `Hashable` derived without `Eq`
--> src/types.ori:1:10
|
1 | #derive(Hashable)
| ^^^^^^^^
|
= note: hash equality invariant: a == b implies a.hash() == b.hash()
= help: also derive `Eq`: #derive(Eq, Hashable)
Examples
Complete Data Type
#derive(Eq, Hashable, Comparable, Clone, Debug)
type User = {
id: int,
name: str,
email: str,
created_at: Duration,
}
Sum Type
#derive(Eq, Clone, Debug)
type JsonValue =
| Null
| Bool(bool)
| Number(float)
| String(str)
| Array([JsonValue])
| Object({str: JsonValue})
Generic Container
#derive(Eq, Clone, Debug)
type Box<T> = { value: T }
// Box<int>: Eq + Clone + Debug
// Box<[int]>: Eq + Clone + Debug
Spec Changes Required
Update 06-types.md
Expand Derive section with:
- Complete list of derivable traits
- Derivation rules for each trait
- Field constraint requirements
- Generic type conditional derivation
Update 07-properties-of-types.md
Add cross-reference to derive semantics.
Summary
| Trait | Derivable | Struct | Sum Type | Requirement |
|---|---|---|---|---|
Eq | Yes | Yes | Yes | All fields: Eq |
Hashable | Yes | Yes | Yes | All fields: Hashable |
Comparable | Yes | Yes | Yes | All fields: Comparable |
Clone | Yes | Yes | Yes | All fields: Clone |
Default | Yes | Yes | No | All fields: Default |
Debug | Yes | Yes | Yes | All fields: Debug |
Printable | Yes | Yes | Yes | All fields: Printable |
Iterator | No | — | — | — |