Standard library

mu keeps the host builtin surface small and grows capability through mu-written modules.

Three layers

  1. Host builtins (Go)
    • A fixed set of 27, like len, append, error, type, inspect, syscall, buf, and dlcall. Run help() for the full list.
    • A capability earns a place here only when mu genuinely cannot express it — it needs the kernel, raw memory, the loader, the scheduler, the runtime type tag, or a mutation mu has no other way to perform. Every one of them is implemented four times over (Go interpreter, Go native, self-hosted interpreter, self-hosted native), so the table is kept deliberately small.
  2. µ-builtins helpers (mu)
    • Ambient globals from lib/builtins.mu, like print, panic, test, and exit — plus input, bool, values, has, int, insert, is_error, must and try. They are ordinary closures or macros written in mu: type(bool) is "CLOSURE".
  3. Namespaced modules (mu)
    • Import explicitly and call helpers behind a namespace, e.g. strings.split.
    • The µ-builtins module is ambient, not importable: use has(m, k) directly; import "builtins" is refused.

Key modules

help(name) prints a module’s documentation and its exports, so import "decimal" then help(decimal) is usually faster than reading source.

Public surface

Documented non-underscore names are the stable module API. Names beginning with _ are implementation helpers, even when they live in an importable helper module. The language does not enforce private module members yet, so the stdlib uses this convention to keep source and help() output honest.

Shapes and protocols

Plain functions are the default. When a recurring map shape or behavior contract appears, define a constructor or protocol-shaped helper instead of adding language syntax.

shape.new(type_name, fields, methods?) creates a tagged map whose __type field makes type(value) report the domain name. result.Result uses this pattern today; richer values such as Decimal also carry behavior through methods and __ops.

Example

import "strings"

line := "  hello,mu  "
trimmed := strings.trim(line)
print(trimmed)

Lazy iterators

iter builds pull-driven iterators on tasks and channels. Values are produced on demand, so a chain over an endless source only does the work the consumer asks for:

import "iter"

naturals := iter.iterate(1, fn(n) { return n + 1 })
squares := iter.map(naturals, fn(n) { return n * n })

print(iter.collect(iter.take(squares, 5)))   // [1, 4, 9, 16, 25]

Iterators are written with two macros the module exports — foreach to pull a source until it is dry, and emit to publish a value and return if the consumer has cancelled:

fn evens(source) {
  return iter.iterator(fn(yield) {
    iter.foreach(value, source, fn() {
      if value % 2 == 0 {
        iter.emit(yield, value)
      }
    })
    return nil
  }, source)
}

They are macros rather than functions because both return on behalf of the function that called them — see macros for why that cannot be a call, and what foreach gives you that a callback cannot.

A consumer that stops early should say so, with iter.stop(source): it cascades to everything upstream, so an abandoned producer is not left parked.

Guidelines

The stdlib stays small, composable, and explicit. New helpers should return errors as values, avoid hidden global state, and avoid compatibility aliases when one canonical name is clear.

See docs/StdlibGuidelines.md for the detailed conventions.

Next steps