Classes
Mux provides object-oriented programming through classes and interfaces (traits).
Basic Class Definition
Key Points:
- Fields must have explicit types (no
autoinference) - Methods use
selfto access instance fields - Methods follow same rules as regular functions
Class Instantiation
Classes use the .new() method pattern:
Design Note: Mux uses explicit .new() rather than direct constructor calls to distinguish class instantiation from function calls and enum variant construction. The .new() method will always instantiate a new object with all default "zero" values for fields, and then you can set fields afterward. This is a simple and consistent pattern for object creation.
The Mux style for constructors is to use a common factory method (see below) that creates and initializes the object. Name factories by behavior (from(...), from_<source>(...), with_<feature>(...)) rather than new.
Interfaces (Traits)
Interfaces define required methods that classes must implement:
Implementing Interfaces
Use the is keyword to implement interfaces:
Note: Use is instead of implements (like Java). Multiple interfaces separated by commas.
Methods
Instance Methods
Access instance data via self:
Methods with Unused Parameters
Static Methods with common
The common keyword declares static (class-level) methods:
common vs const
| Keyword | Purpose | Usage |
|---|---|---|
common | Static methods and factory functions | ClassName.method() |
const | Immutable constants | const int MAX = 100 |
Key Differences:
- Instance methods (no keyword) operate on
selfand require an instance - Static methods (
common) have noselfand are called on the class - Const fields are immutable instance/class fields, not methods
- Static methods cannot access instance fields (no
selfcontext)
Constants in Classes
Classes can have constant (immutable) fields:
Const Enforcement:
- Cannot reassign:
self.MAX_RETRIES = value-> ERROR - Cannot increment/decrement:
self.MAX_RETRIES++-> ERROR - Use
constfor fields that shouldn't change after initialization
Generic Classes
Classes can be generic over type parameters:
See Generics for more details.
Built-in Capabilities
Four capabilities are built into the language rather than declared. A class opts in by naming one and writing the method it requires, and the operators then work on that class:
| Capability | Method | What it enables |
|---|---|---|
Equatable | eq(Self) returns bool | == and != |
Comparable | cmp(Self) returns int | <, <=, >, >=, and == |
Hashable | hash() returns int and eq | use as a map key or set member |
Stringable | to_string() returns string | to_string() on the class |
cmp returns negative, zero or positive like C's strcmp, so one method
supplies every ordering operator. Comparable and Hashable each grant
Equatable, so a class needs only the one that fits - and Hashable requires
eq as well as hash, because a hash alone cannot tell two keys in one bucket
apart.
A class declaring Hashable can key a map, and instances match by their fields
rather than by identity:
A generic class cannot declare Equatable, Comparable or Hashable:
those are registered with the runtime once per class, and a generic class
shares one registration across every instantiation. Stringable registers
nothing and is available to generic classes.
Interface Dispatch (Static)
Mux uses static dispatch for interfaces - no runtime vtable lookup. That shapes how you use them: an interface is a bound on a type parameter, not a type you can store a value in.
Writing the interface as a value type is an error, and says so at the declaration rather than at the call:
Why Static Dispatch?
- Zero cost: No pointer indirection, direct function calls
- Inlining: LLVM can inline interface methods
- Optimization: Better branch prediction, no indirect jumps
The tradeoffs: interfaces cannot be added to types from other modules (no
"extension traits"), and there are no heterogeneous collections - a
list<Drawable> holding both a Circle and a Square needs dynamic dispatch,
which Mux does not have. Model a closed set of alternatives as an enum
instead.
Building a Class From a Document
Every class gets three deserializers, synthesized the way new is. Declare the
shape you expect, and get it or an error saying what was wrong:
The name says what shape it returns:
| Method | Returns |
|---|---|
Config.from_json(text) | result<Config, string> - one object |
Config.list_from_json(text) | result<list<Config>, string> - a JSON array |
Config.list_from_csv(text) | result<list<Config>, string> - the rows of a table |
There is deliberately no singular from_csv: a CSV document is a table, so a
singular form would only work for a file with exactly one row.
The rules
| Case | Result |
|---|---|
| A required field is missing | error naming the field |
An optional<T> field is missing | none |
An optional<T> field is null | none |
| A field is the wrong kind | error naming the field and the type expected |
| The document has fields you did not declare | ignored |
Absence and an explicit null deliberately mean the same thing. Extra fields
are ignored so a server adding one does not break a program that reads it.
Shapes it understands
An error inside a nested class names the field that was actually wrong, not the
one that contained it - missing required field 'qty', not "bad shipping".
Data whose shape you cannot declare
JSON allows a heterogeneous array - [1, "two", true] is valid - and no Mux
type holds those together. Declare such a field as Json and read each entry on
its own terms:
This is the escape hatch, and the reason Json accessors
still exist: everything with a knowable shape goes through a class, and the rest
goes through Json.
CSV is different
A CSV cell is always text, so 3 in a file is the string "3". An int
column is therefore parsed rather than type-checked, and an unparseable cell
names the column:
column 'qty': expected an intFor the same reason a CSV column can only be a string, int, float, bool
or an optional of those - a nested class cannot come out of a single cell. And
optional there means the column may be absent from the header; within a
row that has the column, an empty cell is an empty string, because CSV cannot
tell "empty" from "absent" once the column exists.
Best Practices
- Fields must be explicitly typed - No
autofor class fields - Use interfaces for polymorphism - Define common behavior
- Use
commonfor factory methods - Create instances with pre-populated data - Keep classes focused - Single responsibility principle
- Use
constfor immutable fields - Prevent accidental modification - Leverage generic classes - Reusable data structures
- Prefer static dispatch - Better performance than dynamic dispatch
See Also
- Generics - Generic classes and type parameters
- Interfaces - Built-in interfaces for common operations
- Memory - Reference counting and object lifecycle
- Functions - Method definitions