Skip to main content

Classes

Mux provides object-oriented programming through classes and interfaces (traits).

Basic Class Definition

basic_class.mux
Loading...

Key Points:

  • Fields must have explicit types (no auto inference)
  • Methods use self to access instance fields
  • Methods follow same rules as regular functions

Class Instantiation

Classes use the .new() method pattern:

class_instantiation.mux
Loading...

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:

interfaces.mux
Loading...

Implementing Interfaces

Use the is keyword to implement interfaces:

implementing_interfaces.mux
Loading...

Note: Use is instead of implements (like Java). Multiple interfaces separated by commas.

Methods

Instance Methods

Access instance data via self:

instance_methods.mux
Loading...

Methods with Unused Parameters

class_unused_params.mux
Loading...

Static Methods with common

The common keyword declares static (class-level) methods:

static_methods.mux
Loading...

common vs const

KeywordPurposeUsage
commonStatic methods and factory functionsClassName.method()
constImmutable constantsconst int MAX = 100

Key Differences:

  • Instance methods (no keyword) operate on self and require an instance
  • Static methods (common) have no self and are called on the class
  • Const fields are immutable instance/class fields, not methods
  • Static methods cannot access instance fields (no self context)

Constants in Classes

Classes can have constant (immutable) fields:

class_constants.mux
Loading...

Const Enforcement:

  • Cannot reassign: self.MAX_RETRIES = value -> ERROR
  • Cannot increment/decrement: self.MAX_RETRIES++ -> ERROR
  • Use const for fields that shouldn't change after initialization

Generic Classes

Classes can be generic over type parameters:

class_generics.mux
Loading...

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:

CapabilityMethodWhat it enables
Equatableeq(Self) returns bool== and !=
Comparablecmp(Self) returns int<, <=, >, >=, and ==
Hashablehash() returns int and equse as a map key or set member
Stringableto_string() returns stringto_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.

class_capabilities.mux
Loading...

A class declaring Hashable can key a map, and instances match by their fields rather than by identity:

class_as_map_key.mux
Loading...

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.

static_dispatch.mux
Loading...

Writing the interface as a value type is an error, and says so at the declaration rather than at the call:

Mux
Loading...

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:

from_json.mux
Loading...

The name says what shape it returns:

MethodReturns
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

CaseResult
A required field is missingerror naming the field
An optional<T> field is missingnone
An optional<T> field is nullnone
A field is the wrong kinderror naming the field and the type expected
The document has fields you did not declareignored

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

nested_shapes.mux
Loading...

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:

escape_hatch.mux
Loading...

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 int

For 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

  1. Fields must be explicitly typed - No auto for class fields
  2. Use interfaces for polymorphism - Define common behavior
  3. Use common for factory methods - Create instances with pre-populated data
  4. Keep classes focused - Single responsibility principle
  5. Use const for immutable fields - Prevent accidental modification
  6. Leverage generic classes - Reusable data structures
  7. 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