# Types and Protocols
Deft is dynamically typed with optional type hints (see
[Functions](functions)). For organising data and behaviour at scale,
the language provides `deftype` (declare a record/map shape),
`defprotocol` (declare a method spec set), `defimpl` (attach behaviour
to a type), and `defmethod` (add a single method to an existing impl).
## Quick Start
# Define a record with typed fields. deftype Point %{ :x %{:type :number} :y %{:type :number} } # Construct by tagged-map literal. set p %Point{ :x 3 :y 4 } # Attach behaviour via a protocol. defprotocol Drawable %{ draw {ctx bounds} } defimpl Point Drawable { draw {|ctx bounds| @doc "Draw an asterisk at the point's coordinates." [draw/cell $ctx $self~x $self~y "*"] } } # Or define a standalone method via defmethod. defmethod Point magnitude {|| [math/sqrt (($self~x * $self~x) + ($self~y * $self~y))] } echo [$p magnitude] # => 5
## `deftype` — Record Definitions
`deftype` declares a tagged map with field schemas. Each field is a map
of options describing its type and metadata.
deftype User %{ :name %{:type :string :required true} :email %{:type :string} :age %{:type :number :default 0} :roles %{:type :list :default @{}} :active %{:type :boolean :default true} }
### Field options
| Option | Purpose |
|---|---|
| `:type` | Type hint name (`:string`, `:number`, `:list`, ...) or another `deftype` name |
| `:required` | If `true`, construction fails when the field is missing |
| `:default` | Used when the field is omitted at construction |
| `:description` | Human-readable description surfaced by tooling |
> Use keyword keys (`:type`, not `"type"`) in schema maps. Older docs
> used string keys (`%{ type "string" }`); that form is deprecated.
### Construction
Tagged-map literals produce typed values:
set alice %User{ :name "Alice" :email "alice@example.com" :roles ^{:admin :user} }
Field access uses `~`:
echo $alice~name # => Alice echo $alice~roles # => ^{:admin :user}
A `deftype` is just a tagged map. You can pattern-match on the tag, use
`is?`, or `satisfies?`:
[is? "User" $alice] # => true [type $alice] # => User
### Kwarg construction (`[Type --field value ...]`)
Tagged-map literals (`%User{ :name "Alice" }`) are the canonical
construction form, but for types with many fields, defaults, or required
constraints, the **kwarg constructor** reads better and validates more
strictly. Prefix it with the type name and pass `--field value` pairs:
deftype User %{ :first %{:type :string :required true} :last %{:type :string :required true} :dept %{:type :string :default "accounts"} :active %{:type :boolean :default true} } set u [User --first "Alice" --last "Smith"] echo $u~first # => Alice echo $u~dept # => accounts (default applied) echo $u~active # => true (default applied) set v [User --first "Bob" --last "Lee" --dept "eng"] echo $v~dept # => eng (overridden)
Any **single arg starting with `--`** opts the whole call into kwarg mode.
In kwarg mode:
| Rule | Behaviour |
|---|---|
| Field names | Each `--flag` must match a declared field (or a field inherited via `<<`). Unknown flags are a hard error. |
| `:required` | Enforced — missing required fields raise at construction. |
| `:type` | Checked — mismatched values raise at construction. |
| `:default` | Applied for any field the call omits. |
| Boolean shortcut | A bare `--flag` with no following value means `true` (only valid on `:boolean` fields). |
| Positional args | Not allowed. Once you opt in with `--`, every arg must be a flag. |
[User --first "a" --last "b" --bogus "x"] # ✗ unknown field '--bogus' [User --first "only"] # ✗ missing required ':last' [User "posfirst" --last "poslast"] # ✗ positional args rejected in kwarg mode [Toggle --admin] # ✓ boolean shortcut → admin=true [Toggle --admin false --disabled true] # ✓ explicit boolean values
The positional constructor (`[Point 10 20]`) is unchanged and remains the
right choice when field order is obvious and stable — typically for
small, positional-natural records like `Point` or `Size`. Reach for the
kwarg form when there are ≥3–4 fields, several have defaults, or
readability of the call site matters.
### Relation to flag-collection parameters
The kwarg constructor and the function-level flag-collection parameter
(see [Functions](functions), › Named Optional Arguments) share the same
flag-grammar and the same schema. A trailing `opts^Opts` parameter
desugars into constructing `%Opts{...}` from the trailing `--flag value`
args, so anything you can express with the type's kwarg constructor works
the same way at the call site of a function:
deftype DeployOpts %{ :replicas %{:type :number :default 1} :dry-run %{:type :boolean :default false} } def deploy {|app^string opts^DeployOpts| echo "deploying $app with $opts~replicas replicas" } deploy "billing" --replicas 5 --dry-run # opts is built as if by: %DeployOpts{ :replicas 5 :dry-run true }
### Extending existing types
`<<` lets one type inherit another's fields (mirrors the append operator — it
updates the type on the left with the parent's fields):
deftype Animal %{ :name %{:type :string} } deftype Dog << Animal %{ :breed %{:type :string} } set rex %Dog{ :name "Rex" :breed "Malinois" } echo $rex~name # => Rex
## `defprotocol` — Method Spec Sets
A protocol is a named set of method specs (name + arity). `defimpl`
methods are validated against those specs at load time.
defprotocol Drawable %{ draw {ctx bounds} } defprotocol Measurable %{ measure {} natural-height {} } defprotocol Focusable %{ accepts-focus? {} on-focus {} on-blur {} }
The method spec block uses the same `%{...}` map syntax. Each entry is
a method name with the parameter names it expects. Type hints on params
are allowed:
defprotocol Gridable %{
row-count {}
col-count {}
cell {row col value opts^CellOpts}
}
Both `%{...}` and `^{...}` work for the spec block.
### Spec blocks are load-bearing
The declared method specs (name + arity) are registered in the type
registry, not just the protocol name. `defimpl` methods are validated
against those specs — a method that isn't part of the protocol (almost
always a typo, e.g. `onkey` vs `on-key`) logs a warning at load time
rather than silently creating an unreachable method.
defprotocol Keyable %{ on-key {event bounds} } defimpl MyWidget Keyable { on-key {|event bounds| ... } # ✓ matches spec onkey {|event bounds| ... } # ⚠ warned — not in spec }
Dispatch itself remains name-based (`resolveMethod(type, "on-key")`);
the specs are for validation and `satisfies?`, not runtime routing.
## `defimpl` — Attaching Behaviour
`defimpl { ... }` defines all of a protocol's methods
on a type in a single block:
deftype Point %{ :x %{} :y %{} } defimpl Point Drawable { draw {|ctx bounds| [draw/cell $ctx $self~x $self~y "*"] } } defimpl Point Measurable { measure {|| %{ :w 1 :h 1 } } natural-height {|| 1 } }
`$self` inside an impl body refers to the receiver. Methods are
dispatched based on the receiver's type tag.
### Dispatch
Calls dispatch by name + receiver type. Use bracket form to call:
def p %Point{ :x 3 :y 4 } [draw $p $ctx $bounds] # routes to Point's draw method [$p measure] # also works (postfix-style)
### Multi-protocol impls
You can also implement multiple protocols in one block when the methods
don't clash:
defimpl MyWidget [Drawable Keyable] {
draw {|ctx bounds| ... }
on-key {|event bounds| ... }
}
> Prefer separate `defimpl` blocks per protocol — it's clearer at the
> cost of one extra line each.
### Built-in protocols
Deft ships a number of built-in protocols. The widget-level ones
(`Drawable`, `Measurable`, `Hittable`, `Keyable`, `Container`,
`Textable`, `Editable`, `Selectable`, `Focusable`, `Scrollable`) are
declared in `packages/ui/src/protocols.dft` and used pervasively by TUI
apps. See [TUI: Widgets and Protocols](../06-TUI/widgets-and-protocols).
The four panel-level protocols (`Panel`, `Focusable`, `Scrollable`,
`AppLifecycle`) are Zig-registered at TUI startup and carry specs the
same way:
| Protocol | Methods |
|---|---|
| `Panel` | `draw {ctx}`, `on-event {event}`, `layout {bounds}`, `children {}` |
| `Focusable` | `on-focus {}`, `on-blur {}`, `accepts-focus? {}` |
| `Scrollable` | `on-scroll {delta}`, `scroll-offset {}` |
| `AppLifecycle` | `on-init {}`, `on-suspend {}`, `on-resume {}`, `on-deinit {}` |
Plus any user-defined protocols, all carrying specs the same way.
## `defmethod` — Single-Method Definition
`defmethod` adds one method to an existing (or future) impl. Useful
when one method is large enough to want its own file or scope.
defmethod Point magnitude {|| [math/sqrt (($self~x * $self~x) + ($self~y * $self~y))] } defmethod Point describe {|| "Point($self~x, $self~y)" }
Parameters live inside `|...|`. `||` for zero params.
## `satisfies?` — Protocol Membership
`satisfies?` returns whether a value's type implements a protocol:
def p %Point{ :x 3 :y 4 } [satisfies? $p "Drawable"] # => true [satisfies? $p "Measurable"] # => true (if defined) [satisfies? $p "Container"] # => false
## Tagged Maps
A `deftype` is essentially a tagged map — a map with a leading tag.
You can construct tagged maps directly without a `deftype` declaration:
set ok-result %Ok{ :value 42 } set err-result %Err{ :message "broken" } [is? "Ok" $ok-result] # => true [type $ok-result] # => Ok match $result { %Ok{ :value $v } => $v %Err{ :message $m} => [throw $m] }
### Tag helpers
[tag $somemap :Foo] # add or change a tag [untag $foo-map] # strip the tag
`Ok` and `Err` are the canonical tagged maps used by the Result type —
see [Error Handling](error-handling).
## Type Introspection
For runtime type checks without hints:
[type 42] # => "number" [type "hi"] # => "string" [type @{ 1 2 }] # => "list" [type %Point{...}] # => "Point" [is? "number" 42] # => true — type name as a STRING [is? "Point" $p] # => true [nil? $x] # => true if $x is nil [empty? $xs] # => true if $xs is empty
> `is?` takes the type name as a **string** — `is? "number" x`, not
> `is? :number x`. The keyword form is no longer supported.
## Comparison: Type Hints vs `deftype`
| Aspect | Type Hints | `deftype` |
|---|---|---|
| Purpose | Annotate params / vars | Define data structures |
| Scope | Single def | Module-wide |
| Validation | On function call | On construction |
| Schema | Single type name | Per-field map of options |
| Reuse | Inline | Reusable across functions |
They're complementary — `deftype` defines the structure, type hints
reference it at function boundaries.
## See Also
- [Functions](functions) — parameter syntax and `^` hints
- [Pattern Matching](pattern-matching) — destructuring `%Type{...}` and `%{...}`
- [Metaprogramming](metaprogramming) — `info/types`, `info/protocols`, reflection
- [TUI Widgets and Protocols](../06-TUI/widgets-and-protocols) — built-in UI protocols