This documentation is also published as Markdown for efficient machine reading: the whole site is indexed at /llms.txt, and every page has a clean Markdown copy at the same URL with .md appended. These are generated from the same source and cost far fewer tokens to read than this rendered HTML.

Skip to main content Skip to navigation
Standard Library

Standard Library

The standard library is Mew, not compiler machinery. It ships beside the compiler as .mew source, and every file in it is loaded into every compilation, so nothing in it needs a #load.

Being loaded is not the same as being in scope without a prefix. Those are decided separately: the shipped folder decides what is loaded, and each file's namespace decides what it is called. Most of the library is namespaced, and is reached either by qualifying it or by importing it with use.

mew
use std;
  
println(std.convert.itoa(42));   // qualified
mew
use std;
use std.convert;
  
println(itoa(42));               // imported, so no prefix

Always in scope

Four declarations are in the global namespace, so they need neither a #load nor a use.

Name Why it is global
Option<T> A value that may be absent, described below
Result<T, E> An operation that may fail, described below
Enumerable<T> for resolves it by name
Enumerator<T> for resolves it by name

Enumerable<T> and Enumerator<T> are described under loops, and carry the operators below.

Sequences

Enumerable<T> carries seven operators as members the interface supplies, so everything a for can walk has them, arrays included.

Signature Answers
map<U>(apply: fn(T) -> U) -> Enumerable<U> Every element through apply
filter(keep: fn(T) -> bool) -> Enumerable<T> The elements keep says to keep
fold<A>(seed: A, combine: fn(A, T) -> A) -> A One value, combine run over every element
find(keep: fn(T) -> bool) -> Option<T> The first element keep says to keep
any(keep: fn(T) -> bool) -> bool Whether keep says to keep any of them
count() -> i32 How many elements there are
to_array() -> T[] The elements, in an array

Each takes a lambda, and map and filter answer with a sequence, so they chain.

mew
use std;
  
pub type Person {
    pub field name: string;
    pub field age: i32;
}
  
let people = new Person[] {
    new Person { name: "ada", age: 11 },
    new Person { name: "patrik", age: 44 },
    new Person { name: "valentina", age: 46 },
};
  
let adults = people
    .filter(|p| p.age >= 18)
    .map(|p| p.name)
    .to_array();
  
println($"{adults.count}");
2

map and filter do no work when they are called. They answer with a sequence that walks the one it was built from, so a chain of them walks the source once, when something asks for the elements. The other five ask: fold, count and to_array walk all of it, and find and any stop at the first element that answers.

mew
use std;
  
let values = new i32[] { 1, 2, 3, 4 };
  
println($"{values.fold(0, |total, n| total + n)}");
println($"{values.any(|n| n > 3)}");
println($"{values.find(|n| n > 2).unwrap()}");
10
true
3

find answers with an Option<T>, since a sequence need not hold what was asked for.

Note

to_array walks the sequence twice, once to measure it and once to fill the array, because an array is fixed size. Everything else walks once.

std

Writing text and stopping the program. Everything here needs use std; or the std. prefix.

Signature Does
print(value: string) -> void Writes the text, with no line break
println(value: string) -> void Writes the text, followed by a line break
eprint(value: string) -> void The same, to standard error
eprintln(value: string) -> void The same, to standard error, with a line break
panic(reason: string) -> void Writes the reason and ends the program with exit code 1
exit(code: i32 = 0) -> void Ends the program with the given code, or with 0
mew
use std;
  
println("Hello, world!");
  
print("no newline here");
println("");

print and println write to standard output, eprint and eprintln to standard error. Which one a program reaches for is which stream the reader is expected to be reading: output belongs on the first, and anything about the run itself on the second.

mew
use std;
  
eprintln("reading the file took longer than expected");
println("done");

All four take text, so anything else is turned into text first. Interpolation is how, and it is the only way a value becomes text, so what a program prints reads the same wherever it was written.

mew
use std;
  
let name = "world";
let count = 3;
  
println($"{name} has {count}");
println($"{count}");

A value whose type is any cannot be printed. Interpolation has no text for it either, so say what it is first.

mew
use std;
  
let boxed: any = 3;
  
println($"{boxed as i32}");

Stopping early

panic is for the case where carrying on would be worse than stopping.

mew
use std;
  
let count = 3;
  
if count < 0 {
    panic("a count cannot be negative");
}
Unhandled error: a count cannot be negative

The reason goes to standard error through eprintln, where the runtime's own failures go, so it does not land in output a caller is reading.

exit stops the program the same way without writing anything, and takes the code to stop with. Called with nothing it stops with 0, the code for a run that went as intended.

mew
use std;
  
let count = 3;
  
if count < 0 {
    exit(2);
}

Both carry the noreturn attribute, so a path that ends in either owes no return. That is what makes unwrap below possible.

Note

These two are the only way a Mew program stops early. There are no exceptions, so nothing catches a panic and nothing runs after it.

Option<T> and Result<T, E>

Two global unions, for a value that may be absent and an operation that may fail. They exist so that neither has to be answered with null.

mew
pub union Option<T> {
    none,
    some(T),
}
  
pub union Result<T, E> {
    ok(T),
    err(E),
}

A union is never null and a match has to handle every case, so a caller cannot read a value that is not there by forgetting to check.

mew
use std;
  
pub fn head(values: i32[]) -> Option<i32> {
    if values.count == 0 {
        return Option<i32>::none;
    }
  
    return Option<i32>::some(values[0]);
}
mew
match head(new i32[] { 3, 4 }) {
    .some(value) => {
        println($"{value}");
    },
    .none => {
        println("nothing there");
    },
}
3

Reading one without a match

Both carry methods for the cases where a full match is more than the question needs.

On Option<T> Gives
is_some() -> bool Whether there is a value
is_none() -> bool Whether there is not
unwrap() -> T The value, or a panic
unwrap_or(fallback: T) The value, or fallback
or(other: Option<T>) This option if it has a value, otherwise other
map<U>(f: fn(T) -> U) An Option<U>, with f run over the value
and_then<U>(f) What f answers, for chaining one option onto another
filter(f: fn(T) -> bool) This option if f answers true, otherwise none
ok_or<E>(error: E) A Result<T, E>, using error for none
On Result<T, E> Gives
is_ok() -> bool Whether it succeeded
is_err() -> bool Whether it failed
unwrap() -> T The value, or a panic
unwrap_or(fallback: T) The value, or fallback
map<U>(f: fn(T) -> U) A Result<U, E>, with f run over the value
map_err<F>(f: fn(E) -> F) A Result<T, F>, with f run over the error
and_then<U>(f) What f answers, for chaining one result onto another
ok() -> Option<T> The value as an option
err() -> Option<E> The error as an option
mew
use std;
  
pub fn divide(left: i32, right: i32) -> Result<i32, string> {
    if right == 0 {
        return Result<i32, string>::err("divide by zero");
    }
  
    return Result<i32, string>::ok(left / right);
}
mew
println($"{divide(6, 2).unwrap_or(0)}");
println($"{divide(6, 0).unwrap_or(0)}");
println($"{divide(6, 0).err().unwrap_or("")}");
println($"{head(new i32[0]).ok_or<string>("empty").is_err()}");
3
0
divide by zero
true

unwrap reads the value and panics when there is none, so reach for it only where the absent case is a bug rather than something to handle.

mew
println($"{divide(6, 2).unwrap()}");
println($"{divide(6, 0).unwrap()}");
3
Unhandled error: unwrapped a result that failed

Note

The message names neither the value nor the error, because T and E can be any type and not every type has a text representation. Use err() and print that yourself when the reason matters.

Working on the value without unwrapping it

map, and_then and filter take a lambda and leave the absent or failed case alone, so a chain of them says what to do with a value without asking whether there is one at every step.

mew
println($"{divide(6, 2).map(|n| n * 10).unwrap_or(0)}");
println($"{divide(6, 0).map(|n| n * 10).unwrap_or(-1)}");
println(divide(6, 0).map_err(|reason| $"failed: {reason}").err().unwrap_or(""));
println($"{divide(12, 2).and_then(|n| divide(n, 3)).unwrap_or(0)}");
30
-1
failed: divide by zero
2

map changes what is held, and_then chains one of these onto another and flattens the result, and filter on an Option<T> drops a value that does not answer the question.

mew
use std;
  
let held: Option<i32> = .some(20);
  
println($"{held.filter(|n| n > 10).unwrap_or(0)}");
println($"{held.filter(|n| n > 100).is_none()}");
20
true

std.convert

Conversions between text and numbers.

Signature Does
itoa(value: i32) -> string The text of an i32
atoi(value: string) -> Result<i32, ConvertError> The i32 a string spells, or why it does not
mew
use std;
use std.convert;
  
let text = itoa(42);
let number = atoi("42").unwrap_or(0);

Not every string spells a number, so atoi answers with a Result rather than a number. ConvertError says which way it failed.

mew
pub union ConvertError {
    invalid,
    overflow,
}
mew
use std;
use std.convert;
  
let text = "42";
  
match atoi(text) {
    .ok(value) => {
        println($"{value}");
    },
    .err(reason) => {
        println(reason.describe());
    },
}
Given Answers
"42" ok(42)
"" err(invalid), "not a number"
"abc" err(invalid), "not a number"
"12abc" err(invalid), "not a number"
"2147483648" err(overflow), "outside the range of an i32"

describe() is the text of a ConvertError, for when the reason is going straight to a reader.

itoa cannot fail, so it hands back a string rather than a Result. It writes into a buffer of its own choosing, and twelve bytes is always enough for an i32, the widest being the eleven characters of -2147483648.

Printing a number needs neither of these. Interpolation already turns one into text, and itoa is for when the text itself is the value you want.

string

The library adds three members to string, which need no use because the type is always in scope.

Signature Does
is_empty() -> bool Whether the string holds no characters
parse_i32() -> Result<i32, ConvertError> The i32 this string spells, or why it does not
string::join(separator: string, parts: ...string) -> string The parts joined, separated
string::join(separator: string, items: Enumerable<string>) -> string The same, for a sequence rather than a list of arguments

is_empty answers what == "" answers, with a name on it.

mew
use std;
  
let name = "";
  
if name.is_empty() {
    println("no name given");
}

parse_i32 is atoi written as a member, for when the string is what you have in hand.

mew
use std;
  
let read = "42".parse_i32().unwrap_or(0);
  
println($"{read}");

join is static, so it is named on the type. One form takes anything a for can walk, an array included; the other gathers the parts written at the call.

mew
use std;
  
let names = new string[] { "one", "two", "three" };
  
println(string::join(", ", names));
println(string::join(", ", "one", "two", "three"));

Native code

The library is ordinary Mew. It reaches the platform the way your own code does, so println calls Console::WriteLine and atoi calls Int32::TryParse. Nothing in it is external, and it carries no attribute you could not write yourself.

Important

This surface is expected to change, and to grow. What belongs in the language, in the library that ships with it, and in a package someone installs has not been settled, so treat this as what exists today rather than as a stable surface.

Mew is a programming language under construction.