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.
use std;
println(std.convert.itoa(42)); // qualified
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.
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.
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 |
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.
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.
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.
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.
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.
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.
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.
use std;
pub fn head(values: i32[]) -> Option<i32> {
if values.count == 0 {
return Option<i32>::none;
}
return Option<i32>::some(values[0]);
}
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 |
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);
}
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.
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.
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.
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 |
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.
pub union ConvertError {
invalid,
overflow,
}
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.
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.
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.
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.