std/core
The bottom of the standard library: Option, Result, Error, the operator and conversion traits, and the control structures that are functions.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/core zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
std/core is what every other package builds on: the types the language itself refers to - Option behind Value?,
Result behind ?, the three ranges behind a..b, Array as the inline storage a list literal adapts to, Buffer as the heap storage everything that grows stands on - the traits the
operators and the conversions go through, and the control flow that is an ordinary function. Everything in it is
re-exported by the prelude, so a file rarely imports it by name - except the three target types, which a file that
branches on where it runs imports on purpose, and Plain, which a bound that lends values to a worker names on purpose.
A data structure is not in here: List, Map and Set are written on top of a storage primitive and live in
std/collections. Nothing in std/core knows about text or numbers - with the one exception that
String is unavoidable in a signature (Show.show, panic). That import points at std/text, which is why the packages of the standard library are
cyclic, and cycles between modules are allowed because nothing runs when a module is imported.
Import#
use Option, Result, Error from "std/core"
use Option.Some, Option.None, Result.Ok, Result.Fail from "std/core"Every name below is already in scope through the prelude, so an import is only needed in a file whose project names another prelude, or to make the dependency explicit.
Declarations#
Option#
type Option<Value> with OrElse<Value> {
case Some(value: Value)
case None
fn isSome(): Bool
fn isNone(): Bool
fn map<Output>(transform: Transform<Value, Output>): Output?
fn flatMap<Output>(transform: Transform<Value, Output?>): Output?
fn filter(predicate: Predicate<Value>): Value?
fn forEach(action: Action<Value>)
fn toList(): List<Value>
fn orElse(fallback: lazy Value): Value
fn okOr<Failure>(error: lazy Failure): Result<Value, Failure>
fn expect(message: String): Value
}
extend<Value: Show> Option<Value> with Show
extend<Value, Target: From<Iterate<Value>>> Option<Target> with From<Iterate<Value?>>A value that may be absent. Value? is sugar for Option<Value>. map, flatMap, filter and forEach mean the same
as everywhere else and run immediately, because an Option is a value and not a pipeline. orElse is OrElse, the trait
?? goes through,
okOr turns it into a Result, toList() brings it into the world of pipelines, and expect(message) panics where
absence is a bug.
Result#
type Result<Value, Failure> with OrElse<Value> {
case Ok(value: Value)
case Fail(error: Failure)
fn isOk(): Bool
fn isError(): Bool
fn map<Output>(transform: Transform<Value, Output>): Result<Output, Failure>
fn mapError<OtherFailure>(transform: (error: Failure) => OtherFailure): Result<Value, OtherFailure>
fn flatMap<Output>(transform: Transform<Value, Result<Output, Failure>>): Result<Output, Failure>
fn forEach(action: Action<Value>)
fn toList(): List<Value>
fn ok(): Value?
fn orElse(fallback: lazy Value): Value
fn expect(message: String): Value where Failure: Show
}
extend<Value: Show, Failure: Show> Result<Value, Failure> with Show
extend< Value, Failure, Target: From<Iterate<Value>>, > Result<Target, Failure> with From<Iterate<Result<Value, Failure>>>The result of an operation that can fail. The postfix ? unwraps Ok or returns the Fail from the surrounding
function, converting the error type through From. map, flatMap and forEach work on the Ok side, mapError on the
Fail side. isOk, isError, ok, orElse, toList and expect are the rest. See
Result for the rules.
Error#
trait Error with Show {
fn cause(): Error?
}"Some error, hand it up." Every error type that carries this trait fits into Result<Value, Error>, which lets an
application pass a failure through layers that have nothing to say about it. Precise error types stay the norm for a
library, because a caller can only match on what a signature names. cause() is the chain, and its default is None, so
a type that has nothing to add implements the trait by writing with Error and nothing else.
Void, Never and panic#
Void is the type with exactly one value, and that value is the keyword literal void. A function without a result type
answers Void, a block that ends in a statement has the value void, and Ok(void) passes it on. Never is the type of
an expression that does not return, which is why panic "..." fits into any expression. panic prints
panic: <message> and the site to standard error and exits with 101; nothing else runs on the way out.
Bool#
Bool with Equals, Hash, Show and Encode. &&, || and ! are built in, they short-circuit, and they cannot be
overloaded, because a trait method would evaluate its argument.
Equals, Compare, Ordering, Hash, combineHashes#
Equals has equals and is ==. Compare has compare, requires Equals, and is <, <=, > and >=; its default
members include min and max. Ordering is what compare answers. Hash has hash, and combineHashes is what a
hand-written hash folds with. Float32 and Float64 are deliberately not Hash, so a float can never be a Map key
and the nan key does not exist.
From, Into, TryFrom, TryInto, Show, LiteralParseError#
Conversions follow From and Into: implementing From provides Into for free through a blanket implementation.
TryFrom is the fallible form and provides TryInto the same way, and text is a source like any other
(Int.tryFrom("42")). One direction of each
pair is the one to implement, and an extend that writes Into or TryInto by hand is told which From or TryFrom
to write instead. Every type has From<Self>, and that conversion is the
value itself. Show has show and is what string interpolation calls; showNested is what a value inside another value
uses, and only String and Char override it. LiteralParseError is what the generated TryFrom<String, ...> of a
literal type answers.
RawValue#
trait RawValue {
fn rawValue(): Int
static fn fromRawValue(value: Int): Self?
}The number a case stands for and back, generated for every type whose cases all carry one (case Read = 1,
Cases that stand for numbers): rawValue() answers the number of the case,
fromRawValue the case of a number or None. It is in the prelude, so it can be a bound without an import, which is
what Flags<Case: RawValue> of std/collections is.
The operator traits#
Add, Subtract, Multiply, Divide, Remainder, Power, Negate, OrElse, Index, MutableIndex,
Slice, MutableSlice. Each one has a type parameter list with defaults, which is why with Add means
Add<Self, Self>. a ** b is Power.power, whose first parameter is the Exponent (an integer is raised by an
Int, a float by a float or by an Int). a[i]
is Index.at, a[i] = v is MutableIndex.set, a[from..to] is Slice.slice, a[from..to] = v is
MutableSlice.replace, and a ?? b is OrElse.orElse, whose fallback is lazy so that it is only evaluated where
there is nothing to give back. MutableIndex requires keys() besides set: the keys a for var item in items visits,
in its order - the indices 0..length() of a List and an Array, the keys of a Map in insertion order
(Changing elements in place).
Range, RangeFrom, RangeTo, Bounds#
native type Range<Value> with Equals, Hash {
start: Value
end: Value
inclusive: Bool = false
}
extend<Value: Compare> Range<Value> with Bounds<Value>
extend Range<Int> with Iterate<Int>, Length
extend<Value: Show> Range<Value> with Show
native type RangeFrom<Value> with Equals, Hash {
start: Value
}
extend<Value: Compare> RangeFrom<Value> with Bounds<Value>
extend RangeFrom<Int> with Iterate<Int>
extend<Value: Show> RangeFrom<Value> with Show
native type RangeTo<Value> with Equals, Hash {
end: Value
inclusive: Bool = false
}
extend<Value: Compare> RangeTo<Value> with Bounds<Value>
extend<Value: Show> RangeTo<Value> with Show
trait Bounds<Value: Compare> {
fn lowest(): Value?
fn highest(): Value?
fn includesHighest(): Bool
fn contains(value: Value): Bool
}Which ends a range has is its type, chosen by the syntax: a..b and a..=b are a Range, a.. a RangeFrom,
..b and ..=b a RangeTo. Nothing is optional, so for index in ..10 and (0..).length() are compile errors where
they are written: Range<Int> is Iterate<Int> and Length, RangeFrom<Int> is Iterate<Int> and endless,
RangeTo<Int> is neither. Bounds<Value> is what all three are and what Slice.slice takes, so every spelling works in
brackets.
Array#
native type Array<Item, const Size: Int> with Iterate<Item>, Length, MutableIndex<Int, Item> {
static fn filled(value: Item): Self
fn keys(): Iterate<Int>
static fn generated(produce: (index: Int) => Item): Self
static fn from(items: Iterate<Item>): Self?
fn length(): Int
fn get(index: Int): Item?
fn at(index: Int): Item
fn iterate(): Iterator<Item>
var fn set(index: Int, value: Item)
var fn fill(value: Item)
fn mapped<Output>(transform: Transform<Item, Output>): Array<Output, Size>
}
extend<Item: Equals, const Size: Int> Array<Item, Size> with Equals
extend<Item: Show, const Size: Int> Array<Item, Size> with Show
extend<Item: Hash, const Size: Int> Array<Item, Size> with HashThe inline storage primitive: a fixed number of items, and the number is part of the type (Array<Float, 16>). No
storage on the heap, no reference count, and copying it copies its items. Size is a const parameter - a literal, a
named const or another const parameter - and there is no arithmetic over one, so an out-of-bounds index the compiler
can work out at the call site is a compile error rather than a panic.
Every way to build one writes its size down. A list literal against an expected Array type goes straight into the
inline slots and its items are counted against Size; a ... inside such a literal is allowed when what it spreads is
itself an Array, and the sizes add up. filled and generated take Size from the expected type, and from counts
at run time and answers Array<Item, Size>?. It is Equals, Hash and Show wherever Item is, and the first two
by index and therefore order-dependent. See Arrays and const parameters.
Buffer#
native type Buffer<Item> with Iterate<Item>, Length, MutableIndex<Int, Item>, MutableSlice, From<Iterate<Item>> {
static fn from(items: Iterate<Item>): Self
native static fn withCapacity(capacity: Int): Self
native static fn holdsClose(): Bool
native fn length(): Int
native fn get(index: Int): Item?
fn at(index: Int): Item
native var fn set(index: Int, value: Item)
var fn update(index: Int, change: (var element: Item) => Void)
native var fn append(value: Item)
native var fn insert(index: Int, value: Item)
native var fn removeAt(index: Int): Item?
var fn swapRemove(index: Int): Item?
fn slice(range: Bounds<Int>): Self
var fn replace(range: Bounds<Int>, values: Self)
native var fn reverse()
native var fn clear()
fn iterate(): Iterator<Item>
fn keys(): Iterate<Int>
}The growable heap storage primitive: a sequence that is written in place while its storage has one owner. buffer[i] = x,
set, append, insert, removeAt, swapRemove, replace, reverse and clear change the storage itself; they copy
it once (copy on write) only when the Buffer value has been copied - const other = buffer, passed or captured by
value - and is then written. Nothing stronger is promised, so keep a big buffer in one var owner and pass it as a var
parameter.
swapRemove(index) answers the item at index and moves the last item into its slot: O(1), the order is not kept, and
None (with no change) answers an index out of range. slice is a copy that owns its storage, unlike the shared slice
of a List, so writing the original never turns into a second copy. Buffer implements only what std/core owns
(Iterate, Length, MutableIndex, MutableSlice, Equals, Show, Hash); the List vocabulary belongs to
std/collections.
Shared and isSame#
Shared<Value> is a box that puts a value in a place several owners can hold - the ad hoc version of a shared type.
isSame(first, second) compares identity and works on a shared object only: on a value the answer would expose whether
the implementation shares storage, so the compiler rejects it.
Plain#
A type whose values hold nothing reference counted, anywhere inside them. The compiler grants it from the layout
of the type, the way it generates Show, and nothing else can: a with Plain or an extend ... with Plain is an
error. It is what lets code read a value through a borrowed pointer without touching a count, which is what a worker
that is lent a part of the caller's memory needs. It is not in the prelude.
trait PlainInt, Float, Bool, Char, the sized numbers and Array<Float, 16> are Plain, and so is every record, variant
and tuple whose fields all are. String, List, Map, Decimal and everything that holds one are not, and neither
is a shared type, a trait-typed value, a function value or a type that contains itself: each of those is held in a
counted block. A type parameter is Plain where its bound says so.
use Plain from "std/core"
type Point {
x: Float
y: Float
}
fn first<Item: Plain>(items: List<Item>): Item {
items[0]
}
print first([1, 2, 3])
print first([Point(x: 1.0, y: 2.0)]).xA refusal names the field or the case that holds the counted type, down to that type:
use Plain from "std/core"
type User {
name: String
age: Int
}
fn first<Item: Plain>(items: List<Item>): Item {
items[0]
}
print first([User(name: "Ada", age: 36)]).age
// error: `User` does not implement `Plain`The message goes on with "its field name: String is not Plain: User.name -> String" and "String is reference
counted".
do, unless, retry and Close#
Control structures that are ordinary functions, because only a construct that binds names or jumps is built in, and the destructor trait.
fn do<Value>(body: () => Value): Value
fn unless(condition: Bool, body: () => Void)
fn retry<Value, Failure>(times: Int, action: () => Result<Value, Failure>): Result<Value, Failure>
shared trait Close {
var fn close()
}Close is the destructor: only a shared type implements it, and no program calls close() - the last release of
the object runs it. using name = value is the binding that pins that release to the end of a block; it is part of
the grammar, not a function of std/core (see Destructors, which also says
what is built of it today).
OperatingSystem, Architecture and ByteOrder#
What the program is compiled for, each with one static current: a compile-time constant that torb build --target
sets, the machine torb runs on by default. A match on one of them keeps only the arm the value selects, while every
arm is type checked on every machine (Compile-time branches). They
are not in the prelude: the import is the statement that a file branches on its target.
type OperatingSystem with Show, Equals, Hash {
case Windows
case Linux
case MacOs
case FreeBsd
case Browser
static current: Self = targetOperatingSystem()
fn isPosix(): Bool
fn show(): String
}
type Architecture with Show, Equals, Hash {
case X64
case Arm64
case Wasm64
static current: Self = targetArchitecture()
fn show(): String
}
type ByteOrder with Show, Equals, Hash {
case LittleEndian
case BigEndian
static current: Self = targetByteOrder()
}use OperatingSystem from "std/core"
fn separator(): String {
match OperatingSystem.current {
.Windows => ";"
.Linux | .MacOs | .FreeBsd | .Browser => ":"
}
}
print(OperatingSystem.Linux.isPosix() && separator().byteLength() == 1)
// prints trueshow() answers the name as the vendor writes it (macOS, FreeBSD), and isPosix() is the question for a branch
that should include an operating system added later. Browser on Wasm64 is the target of the playground, whose
torb runs as WebAssembly in a web page (the playground); isPosix() is false
there.
Predicate, Action and Transform#
The three closure shapes a signature takes most, as aliases: a question about one value, an effect on one value, and the conversion of one value into another. An alias is transparent, so any closure of the shape fits (see Predicate, Action and Transform).
type Predicate<Value> = (value: Value) => Bool
type Action<Value> = (value: Value) => Void
type Transform<Input, Output> = (value: Input) => OutputRelated#
- Compile-time branches - which arm of a
matchon the target is compiled. - Result - the rules of
Resultand?. - Bindings - what
constandvardecide. - Traits - how the operator traits are implemented.
- Predicate, Action and Transform - the rules of the three closure aliases.
- The standard library - the other packages.