Core v0.1.0

Source
Core language intrinsics

Installation

Add Core to the [Dependencies] table of your package's Rux.toml:

[Dependencies]
Core = { Namespace = "Rux", Version = "0.1.0" }

Standard packages are declared by hand: the registry does not serve them, so rux add Rux/Core cannot add them.

Guide

Core declares primitive associated constants, diagnostics, value protocols, and compile-time context.

Core is optional. Scalar types, arithmetic, arrays, inferred string literals, and range syntax work without it. Every primitive type is built into the compiler; Core extends them, and importing a primitive from Core imports its associated constants. Slice and range fields are compiler-owned. A replacement package can provide the same extensions and intrinsic declarations; the compiler assigns no privilege to the name or manifest identity of Core.

What it provides

  • Compile-time context — #target (os, arch, abi, endian, pointer width, data model, object format, triple, HasFeature), #build, #compiler, #source, and #config, together with the enums they are typed by: OperatingSystem, Architecture, ApplicationBinaryInterface, Endianness, DataModel, ObjectFormat, BuildMode, OptimizationMode, OutputKind, TargetFeature.
  • Atomic — AtomicLoad, AtomicStore, AtomicExchange, AtomicCompareExchange and AtomicFetchAdd/Sub/And/Or/Xor act on one integer of 8 to 64 bits, bool or pointer so that no other thread sees them half done, and Fence and SpinHint order and pace a thread without touching memory. Each takes a MemoryOrder written at the call as a case such as MemoryOrder::Acquire, and the compiler refuses the orders an operation cannot have. On x86-64 they are plain moves, xchg and lock-prefixed instructions; on AArch64, ldar/stlr and exclusive-access loops. Rux/Atomic wraps them in a type.
  • Startup — ProcessStartFrame is the address the kernel started an ELF program at, where its argument count, argument and environment pointers and auxiliary vector are; it is null on Windows and macOS, which supply the command line through system calls, and in a shared library. Rux/Process reads arguments and the environment through it.
  • Diagnostics — Assert, DebugAssert, and Panic report and terminate; #Error and #Warn reject or flag an unsupported configuration while compiling and produce no runtime code. A release build removes DebugAssert checks without evaluating their arguments. After the location, the report names the functions that led to the failure, one from Name line each, read from the stack's frame records; a release build that inlined a function names its caller instead. Neither Assert nor Panic unwinds, so no destructor runs on the way out — anything that must be released on a failing path belongs to a caller that saw the failure, which is what a native fallible T ! E is for.
  • Native outcome questions — Succeeded and Failed answer which channel a native fallible T ! E holds without unwrapping it. Native forms have no methods of their own, so these are ordinary generic functions; each takes the outcome by value, so a call's result is asked directly and a named outcome with a move-only payload is passed as <-outcome.
  • Ordering — the result of a three-way comparison, with IsLess, IsEqual, IsGreater, the derived IsLessOrEqual and IsGreaterOrEqual, and Reverse. Absence and failure are not library types: they are the native optional T? and fallible T ! E, with the native unit () for an operation with nothing to report.
  • Interfaces for generic code — Equatable and Comparable borrow their Self-typed operands through non-null references, Hashable gives the 64-bit summary a table keys on, and Iterator/Iterable name the iteration protocol the compiler drives by shape. Sendable is the marker the compiler decides for a value that may move to another thread: every type satisfies it except a reference and one holding a type declared #Unsendable(). Resource types prohibit copying with a bodyless canonical = and release themselves with ~Type. Bounds such as func Sort<T: Comparable>(...) resolve Self to each type argument without dynamic dispatch.
  • Lazy adaptors — Map, Filter, Take, Skip, Enumerate, Zip and Chain wrap any Cursor<T> — a type whose Next returns T?, such as a container's iterator, Walk over a slice, or another adaptor — and compute each item only when it is asked for. Their item types are deduced from the wrapped cursor, so Take(Filter(Map(Walk(items), Square), IsEven), 3) needs no type arguments.
  • Integer arithmetic — AddChecked, SubChecked, and MulChecked report whether the true answer was representable and write the machine's answer either way; AddWrapping, SubWrapping, and MulWrapping name the wrapping that +, -, and * already do; AddSaturating, SubSaturating, and MulSaturating clamp to whichever limit the true answer ran past. MaximumOf and MinimumOf report a type's limits. All are generic over the integer type and work at every width, signed and unsigned, deriving a type's zero, signedness, and limits from one operand rather than being written out per width. The operation comes first in each name because CheckedAdd, CheckedSub, and CheckedMul already name the compiler's uint64 intrinsics, which are emitted inline and are what allocation-size arithmetic is written with.
  • Bit operations — CountOnes, CountZeros, LeadingZeros, TrailingZeros, LeadingOnes, TrailingOnes, RotateLeft, RotateRight, ReverseBits, ReverseBytes, BitAt, BitWidthOf, IsPowerOfTwo, and ShiftRightLogical. All are generic over the integer type and right at every width, signed and unsigned: >> copies the sign bit down on a signed type, so everything that reads a value's bits as bits goes through ShiftRightLogical, which brings zeros in either way.
  • Byte order — ToLittleEndian, FromLittleEndian, ToBigEndian, and FromBigEndian convert a value already in a register; StoreLittleEndian, StoreBigEndian, LoadLittleEndian, and LoadBigEndian move one through raw bytes at any alignment, which is what a file or wire format needs. TargetIsLittleEndian reports this target's own order, established from its storage rather than from a list of known targets.
  • Checked conversion — ConvertChecked reports whether a primitive value survived the conversion and writes the machine's answer either way; ConvertWrapping names the wrapping as already does, and ConvertSaturating clamps to the destination's range. Every check is a round trip in the source type, plus one test for the pair a round trip cannot see — a value and its conversion that disagree in sign, which is how -1 reaches uint64. IsSignedType reports whether a type has values below zero.
  • Float classification — IsNaN, IsZero, IsInfinite, IsFinite, IsNegativeZero, IsSignNegative, IsSignPositive, and SignOf. All are arithmetic rather than bit inspection, so one implementation serves every float width including the software-lowered ones, whose storage carries padding no generic function could locate a sign bit inside of. The sign of a zero, which comparison cannot see, is read from the infinity it divides into.
  • Primitive associated constants — Bits, Bytes, Min, Max and the floating-point set (Lowest, MinPositive, Epsilon, Infinity, NaN) for every integer width and every character width from char8 to char512, width metadata for every boolean width from bool8 to bool512, and the finite and special constant set for every float width from float8 to float512. The scalar widths from char32 to char512 share one range, U+0000 to U+10FFFF, however many bytes they occupy. The string widths expose none: a string is a view over code units rather than a value with a width, so a width or a limit would describe neither it nor the units.

Primitive constants and imports

import Core::int8;

func Main() -> int {
    let minimum = int8::Min;
    let message: char8[..] = "hello";
    return message.length as int + minimum as int;
}

int8 is built into the compiler, so it names the scalar type without an import; import Core::int8; imports Core's extend int8 block, which is what makes int8::Min available. The built-in aliases import their canonical widths: import Core::byte; gives byte::Max, the same constant as uint8::Max, and import Core::bool; gives bool::Bits. Loading a dependency that imports it does not import its APIs into the caller. The same rule applies between source files.

Core declares no primitive type: each primitive file holds one documented extend block. Core defines integer and character limits, width metadata, and finite floating constants using ordinary constant expressions. Native integer metadata uses sizeof, so it follows the compilation target. The float limits of float8 and float16 are literals — float8, the E4M3 format, tops out at 240 and float16 at 65504 — while Infinity and NaN at every width, and every limit of float80, float128, float256 and float512, are bodyless intrinsic const declarations whose values the compiler supplies from the format itself.

Sequence views and ranges

The language supplies read-only T[..] and writable var T[..] views. Neither annotation needs a Core import. Their .data and .length fields are compiler-owned. A view owns no storage, and copying it copies two words. Its backing storage must remain alive while it is used.

func Sum(values: int[..]) -> int {
    var total = 0;
    for value in values { total += value; }
    return total;
}

func Fill(values: var int[..], value: int) {
    for index in 0..values.length { values[index] = value; }
}

func Main() -> int {
    var storage: int[3] = [1, 2, 3];
    let view = storage[..];
    Fill(view, 4);
    return Sum(view) - 12;
}

A writable view weakens to a read-only one implicitly; a read-only view cannot be strengthened. Binding a view with let does not freeze its pointee. [] constructs an empty view when a slice type is expected. Use pointer[..length] to view raw storage, and view[start..end] to select a subview. Pointer ranges require an end because pointers carry no length. These operations use element counts rather than byte counts.

Ranges are written int..int, int..=int, int.., ..int, ..=int, and .. in annotations. Their .start and .end fields exist only when the corresponding bound exists, with no declaration or import required.

Literal text is char8[..], char16[..], or char32[..]; the prefixes are c8, c16, and c32, and an unprefixed literal uses UTF-8. The descriptor length counts code units, excluding the trailing NUL in literal storage. Text can be passed directly to a function taking the corresponding read-only character slice. Slicing can split an encoded scalar, so validated-text operations belong in Text and Unicode. A literal cannot become a writable view.

Core diagnostic messages and compile-time context text use char8[..]. External functions taking raw pointers receive "literal".data. Concrete slice extensions can add methods for one element type; reusable generic algorithms use ordinary generic functions.

Ownership and borrowing

Core methods borrow ordinary values with &T and mutable receivers with &var T; callers pass the value directly and the compiler creates the short-lived borrow. Named move-only values require <- when ownership crosses a binding, call, assignment, return, or consuming receiver. Copyable Core values use generated structural copying. Canonical constructors are type calls such as SemanticVersion(1, 2, 3), and descriptive factories retain their names.

Raw pointers remain where absence, storage, or address arithmetic is part of the contract: slice fields, nullable TryInto* outputs, unchecked checked-arithmetic/conversion destinations, byte-order buffers, and zeroization.

Assert and Panic failures have the same UTF-8, LF-terminated layout on every supported target:

Assertion failed: message
  at Function (path:line:column)

Panic uses Panic: message on the first line. The location uses a package-relative path when the source belongs to the package root, and qualified method or module names are retained.

Example

import Core::{ #target, #Error };

when #target.os {
    .Windows, .Linux, .macOS, .FreeBSD => {},
    else => #Error("Unsupported operating system")
}
import Core::{ Assert, SemanticVersion };

func Major(version: &SemanticVersion) -> uint {
    return version.major;
}

func Main() -> int {
    let version = SemanticVersion(1, 2, 3);
    let answer: int32? = .Some(42i32);
    Assert(Major(version) == 1u, "borrowed version");
    Assert((answer ?? 0i32) == 42i32, "present optional value");
    return 0;
}

Index

Interfaces

NameSummary
CursorAnything that yields items of type T one at a time through Next, which for and every adaptor here drive.
ComparableOrdering: which of two values of one type comes first.
EquatableEquality: whether two values of one type stand for the same thing.
HashableHashing: the 64-bit summary a table uses to find a value quickly.
IteratorA cursor that yields a sequence of items one at a time.
IterableA value that can hand out an Iterator over its contents.
SendableA type whose values may move to another thread: the bound a thread's context and result are written with.

Types

NameSummary
SliceCursor<T>A cursor over the elements of a slice, by copy.
Mapped<I, T, U>The items of a cursor, each passed through a function.
Filtered<I, T>The items of a cursor that pass a test.
Taken<I, T>At most a number of items from the front of a cursor.
Skipped<I, T>The items of a cursor after a number of them.
Indexed<T>An item and its position, counting from zero.
Enumerated<I, T>The items of a cursor with their positions.
Pair<A, B>One item from each of two cursors.
Zipped<I, J, A, B>The items of two cursors side by side.
Chained<I, J, T>The items of one cursor and then of another.
MemoryOrderWhich other memory operations may be reordered across an atomic one.
BuildModeThe high-level mode of the active build profile.
OptimizationModeWhat the active build asked the optimizer to favor.
OutputKindThe kind of artifact the active build produces.
BuildProperties of the active build profile and its output.
SemanticVersionA semantic version, as its three numeric core components.
CompilerProperties of the compiler performing the build.
ConfigThe set of user-defined build values.
OrderingThe answer a three-way comparison gives: which side is smaller, or that neither is.
bool128One of two values, stored in 128 bits.
bool16One of two values, stored in 16 bits.
bool256One of two values, stored in 256 bits.
bool32One of two values, stored in 32 bits.
bool512One of two values, stored in 512 bits.
bool64One of two values, stored in 64 bits.
bool8One of two values, stored in 8 bits.
char128One Unicode scalar value, in sixteen bytes.
char16One UTF-16 code unit.
char256One Unicode scalar value, in thirty-two bytes.
char32One Unicode scalar value.
char512One Unicode scalar value, in sixty-four bytes.
char64One Unicode scalar value, in eight bytes.
char8One UTF-8 code unit.
float128An IEEE 754 binary128 floating-point number.
float16An IEEE 754 binary16 floating-point number.
float256An IEEE 754 binary256 floating-point number.
float32An IEEE 754 binary32 floating-point number.
float512A binary512 floating-point number in the IEEE 754 interchange-format progression.
float64An IEEE 754 binary64 floating-point number.
float8An eight-bit E4M3 floating-point number with IEEE 754 infinities and NaNs.
float80An x87 extended-precision floating-point number.
intA target pointer-sized signed integer.
int128A signed integer in 128 bits, two's complement.
int16A signed integer in 16 bits, two's complement.
int256A signed integer in 256 bits, two's complement.
int32A signed integer in 32 bits, two's complement.
int512A signed integer in 512 bits, two's complement.
int64A signed integer in 64 bits, two's complement.
int8A signed integer in 8 bits, two's complement.
uintA target pointer-sized unsigned integer.
uint128An unsigned integer in 128 bits.
uint16An unsigned integer in 16 bits.
uint256An unsigned integer in 256 bits.
uint32An unsigned integer in 32 bits.
uint512An unsigned integer in 512 bits.
uint64An unsigned integer in 64 bits.
uint8An unsigned integer in 8 bits.
SourceThe location of the expression that reads it.
OperatingSystemAn operating system a build can target.
ArchitectureA processor architecture a build can target.
ApplicationBinaryInterfaceThe calling and layout conventions a target's code follows.
TargetFeatureA processor feature a target may be built with.
EndiannessThe order a target stores a multi-byte value's bytes in.
DataModelThe widths a target's C environment assigns to long and to a pointer.
ObjectFormatThe object-file format a target's compiled code is emitted in.
TargetProperties of the machine the build targets.

Functions

NameSummary
Walk<T>A cursor over the elements of items, so a slice can feed the adaptors.
Map<I, T, U>Each item of source passed through transform, as it is asked for.
Filter<I, T>The items of source for which keep holds.
Take<I, T>The first count items of source, or all of them if it has fewer.
Skip<I, T>The items of source after its first count.
Enumerate<I, T>Each item of source with its position, counting from zero.
Zip<I, J, A, B>The items of first and second in pairs, as long as both last.
Chain<I, J, T>The items of first followed by the items of second.
MaximumOf<T>The largest value the integer type of sample can hold.
MinimumOf<T>The smallest value the integer type of sample can hold.
AddChecked<T>Adds two integers and reports whether the true sum is representable.
SubChecked<T>Subtracts one integer from another and reports whether the true difference is representable.
MulChecked<T>Multiplies two integers and reports whether the true product is representable.
AddWrapping<T>Adds two integers, wrapping around the type's range.
SubWrapping<T>Subtracts one integer from another, wrapping around the type's range.
MulWrapping<T>Multiplies two integers, wrapping around the type's range.
AddSaturating<T>Adds two integers, clamping to the type's range instead of wrapping.
SubSaturating<T>Subtracts one integer from another, clamping to the type's range instead of wrapping.
MulSaturating<T>Multiplies two integers, clamping to the type's range instead of wrapping.
AtomicLoad<T>Reads the value at address atomically.
AtomicStore<T>Writes value to address atomically.
AtomicExchange<T>Writes value to address and returns what was there, as one atomic step.
AtomicCompareExchange<T>Writes desired to address if it holds expected, and returns what it held, as one atomic step.
AtomicFetchAdd<T>Adds value to the integer at address and returns what was there, as one atomic step.
AtomicFetchSub<T>Subtracts value from the integer at address and returns what was there, as one atomic step.
AtomicFetchAnd<T>Combines the value at address with value by bitwise and, and returns what was there, as one atomic step.
AtomicFetchOr<T>Combines the value at address with value by bitwise or, and returns what was there, as one atomic step.
AtomicFetchXor<T>Combines the value at address with value by bitwise exclusive or, and returns what was there, as one atomic step.
FenceOrders this thread's memory operations without touching memory.
SpinHintTells the processor that the thread is waiting in a loop for another thread.
BitWidthOf<T>The number of bits in the integer type of sample.
ShiftRightLogical<T>Shifts an integer right, bringing zeros in at the top whatever its signedness.
BitAt<T>Whether the bit at index is set, counting from the least significant bit.
CountOnes<T>The number of set bits.
CountZeros<T>The number of clear bits.
LeadingZeros<T>The number of clear bits above the highest set bit.
TrailingZeros<T>The number of clear bits below the lowest set bit.
LeadingOnes<T>The number of set bits above the highest clear bit.
TrailingOnes<T>The number of set bits below the lowest clear bit.
RotateLeft<T>Rotates an integer's bits toward the most significant end, wrapping the ones that run off.
RotateRight<T>Rotates an integer's bits toward the least significant end, wrapping the ones that run off.
ReverseBits<T>The value with its bits in the opposite order: bit zero becomes the highest bit and back again.
ReverseBytes<T>The value with its bytes in the opposite order, leaving the bits within each byte where they were.
IsPowerOfTwo<T>Whether the value has exactly one bit set.
CheckedAddAdds two unsigned 64-bit values and reports whether the true sum is representable.
CheckedSubSubtracts one unsigned 64-bit value from another and reports whether the true difference is representable.
CheckedMulMultiplies two unsigned 64-bit values and reports whether the true product is representable.
IsSignedType<T>Whether the integer type of sample has values below zero.
ConvertChecked<From, To>Converts a primitive value to another primitive type and reports whether the true value was representable.
ConvertWrapping<From, To>Converts a primitive value to another primitive type, keeping whatever fits.
ConvertSaturating<From, To>Converts a primitive value to an integer type, clamping to that type's range instead of wrapping.
AssertTerminates the program unless condition holds.
DebugAssertTerminates the program unless condition holds, in builds that keep debug assertions.
PanicTerminates the program, reporting message.
#ErrorRejects the build where it is written, reporting message.
#WarnReports message as a compilation warning where it is written, without rejecting the build.
TargetIsLittleEndianWhether this target stores an integer's least significant byte first.
ToLittleEndian<T>The value's bytes as a little-endian value would hold them.
FromLittleEndian<T>The value a little-endian bit pattern stands for on this target.
ToBigEndian<T>The value's bytes as a big-endian value would hold them.
FromBigEndian<T>The value a big-endian bit pattern stands for on this target.
StoreLittleEndian<T>Writes an integer to raw bytes, least significant byte first.
StoreBigEndian<T>Writes an integer to raw bytes, most significant byte first.
LoadLittleEndian<T>Reads an integer from raw bytes holding it least significant byte first.
LoadBigEndian<T>Reads an integer from raw bytes holding it most significant byte first.
IsNaN<T>Whether the value is not a number.
IsZero<T>Whether the value is either zero.
IsInfinite<T>Whether the value is an infinity, of either sign.
IsFinite<T>Whether the value is an ordinary number: neither a NaN nor an infinity.
IsNegativeZero<T>Whether the value is the zero that carries a minus sign.
IsSignNegative<T>Whether the value carries a minus sign.
IsSignPositive<T>Whether the value carries a plus sign.
SignOf<T>The value's sign as one of -1, +1, or the zero it started from.
Succeeded<T, E>Whether outcome holds a success.
Failed<T, E>Whether outcome holds a failure.
ProcessStartFrameThe address the kernel started the program at, on targets where that is where the arguments are.
ZeroizeOverwrites a byte range with zeros, and is never removed however unread those bytes are afterwards.

Constants

NameSummary
#buildThe active build, as a compile-time value.
#compilerThe compiler performing the build, as a compile-time value.
#configThe build's user-defined values, as a compile-time value.
#sourceThe reading expression's own location, as a compile-time value.
#targetThe machine being compiled for, as a compile-time value.