§The standard library

Basic types and functions provided by the runtime.

import std;

§Contents

§Basic types and functions

§type Boolean

The type of true and false.

§type Integer

The type of integer expressions such as 7 or 0x1F.

§type Float

Type type of floating point expressions such as 1.25.

§type Null

The type of the constant null.

§type Symbol

The type of symbol literals, e.g. #answer.

§type Function

The type of all functions.

§type Module

The type of all modules:

import std;
export func main() {
assert(std.type_of(std) == std.Module);
}

§type NativeObject

The type of native objects that have been exposed to tiro. The tiro runtime manages the lifetime (and the storage) of these objects, which usually includes invoking a native finalizer when the object is collected.

§type NativePointer

The type of a native pointer that has been exposed to tiro. The tiro runtime does not manage these objects in any way, which makes them very cheap to use.

§type Type

The type of all types. In other words, std.type_of(value) will always return an instance of this type for any kind of value.

§func type_of

func type_of(v: Value): Type;

Returns the type of v. For example:

import std;
export func main() {
assert(std.type_of(true) == std.Boolean);
}

§Containers

§type Tuple

The type of all tuples, e.g. (), (1,) or (1, 2).

§type Record

The type of all records, e.g. (:) or (foo: "bar").

§type RecordSchema

The type of all record schemas.

§func schema_of

func schema_of(r: Record): RecordSchema;

Returns the schema associated with the given record.

§type Array

The type of all arrays, e.g. [] and [a, b, c].

An array is a dynamic sequence of items that can grow or shrink as needed. Existing items can be read or written using the indexing operator, e.g. const first = array[0] or array[0] = "first". The sequence of items can be manipulated using the methods below.

§func Array.size

func Array.size(): Integer;

Returns the number of items in the array.

§func Array.append

func Array.append(v: Value);

Appends v to the array.

§func Array.clear

func Array.clear()

Removes all items from the array.

§type Buffer

Buffers reference a raw region of memory, i.e. a blog of bytes. They are especially useful when doing I/O or when manipulating large amounts of data.

TODO: usage

§func Buffer.size

func Buffer.size(): Integer;

Returns the number of items in the buffer.

§type Map

A map is a container that associates keys with values. The key-value associations can be manipulated at will. Given a key, a map allows for efficient lookup (and update) of the associated value.

Maps are implemented as hash tables.

§Insertion order

Contrary to most other hash table implementations, tiro's map preserves the insertion order of its mappings. When iterating a map, keys and values are visited in the order in which they were inserted:

  • A new key-value mapping (i.e. a key that was not already in the map) is always inserted at the end.
  • Updating an existing key-value mapping (i.e. key already exists in the map) does not change its order in the map.
  • Removing a key-value mapping preserves the order of all remaining mappings.
Example: iterating over a map
import std;
export func main() {
const m = map{};
m[1] = "one";
m[2] = "two";
m[3] = "three";
// Guaranteed output:
// 1 one
// 2 two
// 3 three
for (key, value) in m {
std.print(key, value);
}
}

§func Map.size

func Map.size(): Integer;

Returns the number of key-value mappings in the map.

§func Map.contains

func Map.contains(key: Value): Boolean;

Returns true if there is a value associated with key, false otherwise.

§func Map.keys

func Map.keys(): Iterable;

Returns an iterable view over the keys within the map. The iteration order is the same as the map's order.

§func Map.values

func Map.values(): Iterable;

Returns an iterable view over the values within the map. The iteration order is the same as the map's order.

§func Map.clear

func Map.clear();

Removes all key-value mappings from the map.

§func Map.remove

func Map.remove(key: Value);

Removes the key-value mapping for key. Does nothing if there is no such mapping.

TODO: Return true/false?

§type Set

A set is a container that stores unique items. Items can be inserted or removed at will, and their existence can be tested efficiently.

Like maps, they are implemented as hash tables.

§Insertion order

When iterating a set, its items are visited in the order they were inserted in. See also map insertion order.

§func Set.size

func Set.size(): Integer;

Returns the number of items in the set.

§func Set.contains

func Set.contains(item: Value): Boolean;

Returns true if the set contains item, false otherwise.

§func Set.clear

func Set.clear();

Removes all items from the set.

§func Set.insert

func Set.insert(item: Value): Boolean;

Inserts item into the set. Returns true if item has been inserted, false if it already existed.

§func Set.remove

func Set.remove(item: Value);

Removes item from the set. Does nothing if item does not exist in the set.

TODO: Return true/false?

§Strings

§type String

The type of all string expressions. Strings are immutable.

TODO: Unicode strings

TODO: Internal representation is guaranteed to be UTF-8

§type StringSlice

§type StringBuilder

§func new_string_builder

§func debug_repr

func debug_repr(value: Value, pretty = false: Boolean): String;

Returns a debug representation of the given value as a string. When the optional pretty parameter is true, then the value will be pretty printed.

§Floating point math

Basic constants and functions for math operations on floating point numbers (type Float).

All functions return floating point values on success. Input arguments may also be integers, these are then converted to floats internally.

§const PI

const PI: Float;

The approximate value of the circle constant Pi.

§const TAU

const TAU: Float;

The approximate value of the circle constant Tau (2𝜋).

§const E

const E: Float;

The approximate value of Euler's constant e.

§const INFINITY

const INFINITY: Float;

The floating point value of positive infinity.

§func abs

func abs(x: Float | Integer): Float;

Returns the absolute value of x, i.e. |x|.

§func pow

func pow(a: Float | Integer, b: Float | Integer): Float;

Returns a raised to the power b.

§func log

func log(x: Float | Integer): Float;

Returns the natural logarithm (base e) of x.

§func sqrt

func sqrt(x: Float | Integer): Float;

Returns the square root of x.

§func round

func round(x: Float | Integer): Float;

Returns the nearest integral value to x.

§func ceil

func ceil(x: Float | Integer): Float;

Returns the smallest integral floating point value that is greater than or equal to x.

§func floor

func floor(x: Float | Integer): Float;

Returns the largest integral floating point value that is less than or equal to x.

§func sin

func sin(x: Float | Integer): Float;

Returns the sine of x. x must be an angle in radians.

§func cos

func cos(x: Float | Integer): Float;

Returns the cosine of x. x must be an angle in radians.

§func tan

func tan(x: Float | Integer): Float;

Returns the tangent of x. x must be an angle in radians.

§func asin

func asin(x: Float | Integer): Float;

Returns the inverse sine of x in radians.

§func acos

func acos(x: Float | Integer): Float;

Returns the inverse cosine of x in radians.

§func atan

func atan(x: Float | Integer): Float;

Returns the inverse tangent of x in radians.

§Input and output

§func print

func print(values...: Value...);

Prints the given values followed by a new line.

§func loop_timestamp

func loop_timestamp(): Integer;

Returns the current loop timestamp.

The returned value is the number of milliseconds since some arbitrary starting point - usually the start of the program - until the start of the current main loop iteration. The value is guaranteed to be monotonic, i.e. it will never decrease.

The loop timestamp is refreshed on every iteration of the runtime's main loop. A single iteration involves calling all ready coroutines and continues until all coroutines are either done or waiting for the completion of an asynchronous function.

Within the same iteration, loop_timestamp() will always return the same value.

§Coroutines

Tiro's coroutines implement cooperative multitasking within a shared operating system thread. This means that a coroutine will continue running until it gives up control voluntarily. It will never be preempted by a scheduler behind the scenes.

In order to give up control (also called yielding), a coroutine can perform one of the following actions:

  • Call an asynchronous function. Asynchronous functions usually yield behind the scenes automatically, transparent to the caller. They can be implemented in native code or by using the the yield_coroutine() function (like the function lock() in the mutex example).

    While an asynchronous function waits for its completion, other coroutines can execute on the shared operating system thread.

  • Call dispatch(). Other coroutines that are ready to execute will have the chance to run. The current coroutine will resume execution once those coroutines have yielded.

    It can be useful to invoke dispatch() in potentially long loops to avoid blocking the system for too long.

  • Call yield_coroutine(). This will stop the coroutine until it has been manually resumed by another coroutine. Care must be taken when using this primitive function!

§type Coroutine

Coroutines represent lightweight threads of execution within a single operating system thread. They are created through the native API or by calling std.launch().

§type CoroutineToken

§func current_coroutine

func current_coroutine(): Coroutine;

Returns the current Coroutine instance, i.e. the coroutine from which current_coroutine() was called.

§func launch

func launch(fn: Function, args...: Value...): Coroutine;

Executes fn(args...) from within a new coroutine. The coroutine instance is returned by this function.

TODO: Should we return a future instead?

§func dispatch

func dispatch();

Dispatches to other ready coroutines. The current coroutine will resume execution after all other ready coroutines had the opportunity to run.

§func coroutine_token

func coroutine_token(): CoroutineToken;

Returns a CoroutineToken instance suitable for resuming the current coroutine. Coroutine tokens are invalidated after the coroutine has been resumed again. See yield_coroutine() for an example.

WARNING: This is a low level API designed to control the execution of a coroutine manually. It should only be used to create new asynchronous abstractions.

§func yield_coroutine

func yield_coroutine();

Immediately pauses execution of the current coroutine. Execution will only continue once the coroutine has been resumed via a CoroutineToken previously obtained from coroutine_token().

WARNING: This is a low level API designed to control the execution of a coroutine manually. It should only be used to create new asynchronous abstractions.

Example: a simple Mutex
import std;
export func main() {
const mutex = createMutex();
const task = func(id) {
for var i = 0; i < 3; i += 1 {
mutex.lock();
std.print("Hello from ${id}");
std.dispatch(); // Other coroutine can observe a locked mutex
mutex.unlock();
}
};
std.launch(task, "1");
std.launch(task, "2");
}
// Creates a simplistic mutex that only supports two coroutines.
func createMutex() {
// Coroutine token if another coroutine is already waiting.
var waiter = null;
var locked = false;
return (
lock: func() {
if locked {
if waiter != null {
std.panic("Cannot support more than one waiter");
}
// Wait until the other coroutine unlocks.
waiter = std.coroutine_token();
std.yield_coroutine();
}
// At this point, either the mutex wasn't initially locked
// or yield_coroutine() returned, i.e. the other coroutine unlocked.
assert(!locked);
locked = true;
},
unlock: func() {
if !locked {
std.panic("Mutex was not locked");
}
locked = false;
if waiter {
waiter.resume();
waiter = null;
}
// Give other coroutines the opportunity to run
std.dispatch();
}
);
}

§Error handling

§type Result

A result represents the result of an operation that may fail. The result type is the basic primitive type used in tiro's error handling facilities.

Results may be in one of two states:

  • Success. Calling result.value() returns the result of the operation.

  • Error. Calling result.error() returns a value explaining why the operation failed.

Results are constructed by calling the success() and error() functions.

Example: simple error handling
import std;
export func main() {
const result1 = operation(5);
std.print(result1.type(), result1.value()); // #success 10
const result2 = operation(-5);
std.print(result2.type(), result2.error()); // #error value too small!
}
func operation(n) {
if (n < 0) {
return std.error("value too small!");
} else {
return std.success(n * 2);
}
}

§func Result.type

func Result.type(): Symbol;

Returns either #success or #error, depending on the result's state.

§func Result.is_success

func Result.is_success(): Boolean;

Returns true if the result represents success, false otherwise.

§func Result.is_error

func Result.is_error(): Boolean;

Returns true if the result represents an error, false otherwise.

§func Result.value

func Result.value(): Value;

Returns the result's value. Requires that the result represents success.

§func Result.error

func Result.error(): Value;

Returns the result's error. Requires that the result represents an error.

§func success

func success(value: Value): Result;

Returns a new successful Result that holds the given value.

§func error

func error(err: Value): Result;

Returns a new error Result that holds the given err.

§type Exception

Exceptions represent unexpected (and often fatal) errors encountered during program execution. They are generated when code panics, for example by calling panic().

It is often preferable to use the less intrusive Result type instead. Panicking should be reserved for signaling critical error conditions such as programming errors.

TODO: Other properties (stack trace, user data, secondary exceptions)

§func Exception.message

func Exception.message(): String;

Returns the exception's error message.

§func panic

func panic(message: String);

Immediately aborts execution of the current function with an error explained by message. From within the panic function, a new Exception is constructed with the given message and stack unwinding is started. Unwinding can be stopped by using the catch_panic function.

This function is designed to report unexpected errors such as programming errors. Use the Result type instead if errors can be expected, e.g. to report missing files.

§func catch_panic

func catch_panic(fn: Function): Result;

Invokes fn() and stops propagation of any non-fatal panic thrown by fn or its callees. After execution of fn() completes (either through panic or through normal return), a Result is returned:

  • If fn returned normally, its return value is wrapped into a success result and returned.
  • If a panic was caught, an error result is returned. The result's error() is set to the caught exception value.
Example: catching a panic from a function
import std;
export func main() {
const result = std.catch_panic(may_fail);
if (result.is_success()) {
std.print("success:", result.value());
} else {
std.print("error:", result.error().message());
}
}
func may_fail() {
// Replace `false` with `true` to return normally
if (false) {
return 42;
} else {
std.panic("help!");
}
}