Ruta graveolens  ·  notes from a language experiment  ·  cultivated since 2025

Intrinsic Expressions

An intrinsic expression is a builtin that appears in expression position and produces a value.

intrinsic = "@" IDENT "(" [ intrinsic_arg { "," intrinsic_arg } ] ")" ;
intrinsic_arg = expression | type ;

Intrinsics MAY accept expressions, types, or a combination of both as arguments, depending on the specific intrinsic.

Each intrinsic has a fixed signature specifying the number and types of arguments it accepts.

It is a compile-time error to call an intrinsic with the wrong number of arguments.

It is a compile-time error to use an unknown intrinsic name.

Quick Reference

The following tables list every intrinsic the compiler recognizes, grouped by whether the intrinsic may appear in any expression position (expression intrinsics) or only inside a checked block (unchecked intrinsics, specified in §9.2). This inventory is kept in sync with the compiler's intrinsic registry: the pre-interned names in crates/rue-air/src/sema/known_symbols.rs and the dispatch on them in crates/rue-air/src/sema/analysis.rs. A name that is absent from that registry is rejected as an unknown intrinsic (rule 4.13:5), so any intrinsic the compiler accepts MUST appear here.

Expression intrinsics (usable in any expression position):

IntrinsicPurposeArgumentsReturn Type
@dbgPrint debug output1 expression (int, bool, or string)()
@size_ofGet type size in bytes1 typei32
@align_ofGet type alignment in bytes1 typei32
@int_maxLargest value of an integer type (§4.13:126)1 type (integer)that integer type
@int_minSmallest value of an integer type (§4.13:126)1 type (integer)that integer type
@offset_ofGet a struct field's byte offset1 type, 1 field nameu64
@intCastConvert between integer types1 expression (integer)inferred integer type
@bitCastReinterpret an integer's bits at the same width (§4.13:118)1 expression (integer)inferred integer type of the same width
@wrapping_addWrapping (modular) addition (§4.13:97)2 expressions (same integer type)that integer type
@wrapping_subWrapping (modular) subtraction (§4.13:97)2 expressions (same integer type)that integer type
@wrapping_mulWrapping (modular) multiplication (§4.13:97)2 expressions (same integer type)that integer type
@to_stringFormat an integer as its decimal StrBuf (requires a lexical @import("std") in the file; §3.7:22)1 expression (any integer)StrBuf
@dropRun a value's drop glue and consume it (RUE-187)1 expression (any type)()
@read_lineRead line from stdinnoneOption(StrBuf)
@parse_i32Parse text to i321 expression (any text rung)Option(i32)
@parse_i64Parse text to i641 expression (any text rung)Option(i64)
@parse_u32Parse text to u321 expression (any text rung)Option(u32)
@parse_u64Parse text to u641 expression (any text rung)Option(u64)
@random_u32Generate random u32noneu32
@random_u64Generate random u64noneu64
@arg_countNumber of command-line arguments (incl. argv[0])noneu64
@arg_lenByte length of argument i (0 out of range)1 expression (u64 index)u64
@env_countNumber of environment entriesnoneu64
@env_lenByte length of environment entry i (0 out of range)1 expression (u64 index)u64
@target_archGet target architecturenoneArch
@target_osGet target OSnoneOs
@target_data_modelGet target C data modelnoneDataModel
@importImport module1 expression (string literal)module type

Unchecked intrinsics (only valid inside a checked block; see §9.2 for their full semantics):

IntrinsicPurposeArgumentsReturn Type
@syscallDirect system call1–7 expressions (u64)i64
@rawconst pointer to a place1 place expressionptr const T
@raw_mutmut pointer to a place1 place expressionptr mut T
@field_ptrmut pointer to a struct field place1 field-access expressionptr mut F
@ptr_readRead through a pointer1 expression (ptr const T/ptr mut T)T
@ptr_writeWrite through a pointer2 expressions (ptr mut T, T)()
@ptr_read_unalignedRead through a possibly unaligned pointer (§9.2)1 expression (ptr const T/ptr mut T)T
@ptr_write_unalignedWrite through a possibly unaligned pointer (§9.2)2 expressions (ptr mut T, T)()
@ptr_offsetPointer arithmetic2 expressions (ptr T, integer)ptr T
@ptr_to_intPointer to integer1 expression (pointer)u64
@int_to_ptrInteger to pointer1 expression (u64)inferred ptr mut T
@allocAllocate physical bytes with alignment (§9.2)2 expressions (u64 size, u64 align)ptr mut u8
@alloc_zeroedAllocate zero-filled physical bytes (§9.2)2 expressions (u64 size, u64 align)ptr mut u8
@freeFree an allocated block (§9.2)3 expressions (ptr mut u8, u64 size, u64 align)()
@reallocResize an allocated block, possibly moving it (§9.2)4 expressions (ptr mut u8, u64 old size, u64 align, u64 new size)ptr mut u8
@resizeResize an allocated block in place only (§9.2)4 expressions (ptr mut u8, u64 old size, u64 align, u64 new size)bool
@byte_copyCopy size non-overlapping bytes3 expressions (ptr mut u8, ptr const u8/ptr mut u8, u64)()
@byte_moveCopy size possibly overlapping bytes (§9.2)3 expressions (ptr mut u8, ptr const u8/ptr mut u8, u64)()
@byte_setFill size bytes with a byte3 expressions (ptr mut u8, u8, u64)()
@arg_ptrPointer to argument i's bytes (null out of range)1 expression (u64 index)ptr mut u8
@env_ptrPointer to environment entry i's bytes (null out of range)1 expression (u64 index)ptr mut u8

The compiler frontend additionally reserves the names @cast, @panic, @assert, and @test_preview_gate. Their surface syntax is not yet fully stabilized, so they are omitted from the normative inventory above, but they are no longer no-ops (RUE-319):

  • @panic(msg?: text) has type ! (never): it aborts the process and never returns. The optional message may use any canonical text rung; unrelated aggregates are not accepted. It writes panic: <msg> (or just panic when called with no argument) to standard error and exits with status 101 — the same abort discipline as the @intCast overflow, division-by-zero, and bounds-check traps. As a diverging expression it participates in never coercion (3.4:2), so it may appear wherever a value of any type is expected — a typed let initializer, an if/else or match arm whose other arms produce a value, or a bare function tail.
  • @assert(cond: bool, msg?: text) requires an exact boolean condition and the same optional text contract as @panic. When cond is false it aborts exactly like @panic: with a message it writes panic: <msg>, otherwise it writes assertion failed, and in both cases exits with status 101. When cond is true it has no effect. The expression has type () on both paths.
  • @cast is rejected at compile time with a diagnostic directing the programmer to @intCast; it never had a working inference rule and is fully redundant with @intCast. Use @intCast for integer conversions.
  • @test_preview_gate() is a zero-argument no-op that exists only to test the preview-feature gating machinery itself (--preview test_infra); it is test infrastructure, not a language feature.

@dbg

The @dbg intrinsic prints a value to standard output for debugging purposes. It borrows its argument: the value is read but not consumed, so a non-Copy argument (such as a StrBuf) remains valid after the @dbg call and is dropped by its owner at the end of the enclosing scope, exactly as if the @dbg call had not occurred.

@dbg accepts exactly one argument of integer, boolean, or string type.

@dbg prints the value followed by a newline character.

The textual form @dbg prints for its argument is determined by the argument's type: an integer is printed in base 10, with a leading - when a signed integer is negative and no sign otherwise; a boolean is printed as true or false; a StrBuf is printed as its exact bytes, byte-for-byte, with no quoting or escaping (mirroring print, 3.7). The newline of 4.13:8 follows this text.

The return type of @dbg is ().

fn main() -> i32 {
    @dbg(42);           // prints: 42
    @dbg(-17);          // prints: -17
    @dbg(true);         // prints: true
    @dbg(false);        // prints: false
    @dbg(10 + 5);       // prints: 15
    @dbg("hello");      // prints: hello
    0
}

@dbg is useful for inspecting values during development:

fn factorial(n: i32) -> i32 {
    @dbg(n);  // trace each call
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn main() -> i32 {
    factorial(5)
}

@size_of

The @size_of intrinsic returns the size of a type in bytes.

@size_of accepts exactly one argument, which MUST be a type.

The return type of @size_of is i32.

The value returned by @size_of is determined at compile time.

fn main() -> i32 {
    @size_of(i32)     // 4 (i32's natural byte width, observed under the compact layout)
}
struct Point { x: i32, y: i32 }

fn main() -> i32 {
    // 8 under the compact layout (ADR-0052): two four-byte i32 fields, no padding.
    @size_of(Point)   // 8
}

@align_of

The @align_of intrinsic returns the alignment of a type in bytes.

@align_of accepts exactly one argument, which MUST be a type.

The return type of @align_of is i32.

The value returned by @align_of is determined at compile time.

@align_of(T) reports the alignment the implementation has chosen for T under the layout in effect for the compilation (1.3:6, 3.6:12); it observes that choice and does not guarantee a particular value. Under the compact layout (ADR-0052) each scalar has its natural alignment — @align_of(i32) observes 4, @align_of(bool) observes 1, @align_of(i64) observes 8 — and a struct's alignment is that of its most-aligned field, so a portable program must not assume a particular value such as 8. Whichever layout is in effect, the size of a type is always a multiple of its alignment (3.6:8).

fn main() -> i32 {
    @align_of(i32)    // 4 (i32's natural alignment, observed under the compact layout)
}

@int_max and @int_min

The @int_max intrinsic returns the largest value representable in an integer type, and the @int_min intrinsic returns the smallest. The bounds follow the two's-complement ranges of §3.1: for an unsigned type of width w they are 2^w - 1 and 0; for a signed type they are 2^(w-1) - 1 and -2^(w-1).

@int_max and @int_min each accept exactly one argument, which MUST be an integer type (§3.1).

The result type of @int_max(T) and @int_min(T) is T itself. This is the only result type that can represent every bound exactly: @int_max(u64) exceeds every signed type, and @int_min(i64) is below every unsigned type.

It is a compile-time error (E0702) to apply @int_max or @int_min to a type argument that is not an integer type.

The value of @int_max(T) and @int_min(T) is determined at compile time, and — unlike @size_of and @align_of — the integer-bounds intrinsics are comptime-evaluable (4.14:29): the bounds depend only on the identity of T, never on layout, so they may appear in const initializers and comptime argument positions.

fn main() -> i32 {
    if @int_max(u8) == 255 && @int_min(i8) == -128 {
        0
    } else {
        1
    }
}

Because the result is typed at the queried type, the intrinsics compose with generic comptime T code: overflow predicates such as a > @int_max(T) - b need no per-type constants, which is the motivating use (generic checked/saturating arithmetic, RUE-694).

@offset_of

The @offset_of intrinsic returns the byte offset of a field within a struct type, mirroring Rust's core::mem.offset_of!.

@offset_of accepts exactly two arguments: the first MUST be a struct type, and the second MUST be the name of one of that struct's fields.

The return type of @offset_of is u64.

The value returned by @offset_of is the offset the compiler assigns to the field under the layout it chooses for the struct, determined at compile time. Because the value comes from the compiler's own layout rather than a hand-computed constant, @offset_of remains correct even if the struct layout is implementation-defined. Under the compact layout each field is placed at the lowest offset satisfying its alignment after the preceding fields, so an offset accounts for both the preceding fields' sizes and any alignment padding (§3.6).

It is a compile-time error to apply @offset_of to a non-struct type, or to name a field that the struct does not declare.

struct Mixed { a: i32, b: i64, c: bool }

fn main() -> i32 {
    let off_a: u64 = @offset_of(Mixed, a);   // 0
    let off_b: u64 = @offset_of(Mixed, b);   // 8  (i64 field at its eight-byte alignment)
    let off_c: u64 = @offset_of(Mixed, c);   // 16 (bool after the i64)
    let sum: u64 = off_a + off_b + off_c;    // 24
    @intCast(sum)
}

@intCast

The @intCast intrinsic converts an integer value from one integer type to another.

@intCast accepts exactly one argument, which MUST be an integer type (any of i8, i16, i32, i64, u8, u16, u32, u64).

The target type of the conversion is inferred from the context where @intCast is used.

It is a compile-time error if the target type cannot be inferred or is not an integer type.

If the source value cannot be exactly represented in the target type, a runtime panic occurs.

fn main() -> i32 {
    let x: i32 = 100;
    let y: u8 = @intCast(x);  // OK: 100 fits in u8
    @intCast(y)               // Convert back to i32
}
fn takes_u8(x: u8) -> u8 { x }

fn main() -> i32 {
    let x: i32 = 50;
    takes_u8(@intCast(x));    // Target type inferred from parameter
    0
}
// This panics at runtime: 256 doesn't fit in u8
fn main() -> i32 {
    let x: i32 = 256;
    let y: u8 = @intCast(x);  // panic: integer cast overflow
    0
}
// This panics at runtime: negative values don't fit in unsigned types
fn main() -> i32 {
    let x: i32 = -1;
    let y: u32 = @intCast(x); // panic: integer cast overflow
    0
}

@bitCast

The @bitCast intrinsic reinterprets an integer value's bits at another integer type of the same width. It is the bit-preserving counterpart to @intCast, which is value-preserving: @intCast keeps the number and rejects the representations that do not fit, while @bitCast keeps the representation and lets the number change.

@bitCast accepts exactly one argument, which MUST be of an integer type (any of i8, i16, i32, i64, u8, u16, u32, u64). The target type is inferred from the context where @bitCast is used, exactly as @intCast's target type is (4.13:26).

It is a compile-time error if the target type cannot be inferred, is not an integer type, or is an integer type whose width differs from the argument's width (E0950). A reinterpretation neither invents nor discards bits, so it is defined only between the same-width pairs — i8/u8, i16/u16, i32/u32, and i64/u64 — in either direction, and between an integer type and itself. Converting between widths is @intCast's job.

For a source type of width N bits, the value of @bitCast(x) is the value whose N-bit two's-complement representation is identical to that of x. Equivalently, for a target type T: when the shared width is N and the argument's value is x, the result is x if x is in range for T, and otherwise x - 2^N when T is signed and x + 2^N when T is unsigned.

@bitCast never traps. Every value of the source type has a representation at the target type — the same representation — so no operand is rejected, in contrast to the overflow trap of 4.13:28. In particular @bitCast is the operation that moves a u64 whose top bit is set into an i64, which @intCast traps on.

@bitCast is an involution on each same-width pair: for every value x of an integer type S and same-width type T, reinterpreting x at T and then reinterpreting that result back at S yields x.

In a compile-time context, @bitCast is evaluated exactly as @intCast is: it introduces no new constant-evaluation rule, so an invocation that @intCast would not fold into a constant is likewise not a constant expression.

fn main() -> i32 {
    // The u64 values above i64::MAX have no @intCast into i64 at all;
    // @bitCast moves them across, and back, unchanged.
    let big: u64 = 9223372036854775808;   // 1 << 63
    let signed: i64 = @bitCast(big);
    @dbg(signed);                          // -9223372036854775808
    let back: u64 = @bitCast(signed);
    @dbg(back);                            // 9223372036854775808

    // Narrower pairs reinterpret the same way.
    let byte: u8 = 255;
    let sbyte: i8 = @bitCast(byte);
    @dbg(sbyte);                           // -1
    0
}

@read_line

The @read_line intrinsic reads a line of text from standard input.

@read_line accepts no arguments.

The return type of @read_line is the trusted standard Option specialized at StrBuf — the producer-nominal enum declared by std.option.Option (std/option.rue, ADR-0038), instantiated as Option(StrBuf). The intrinsic yields this exact standard specialization in every context: bare as the operand of the ? operator (§4.15), as the initializer of an annotated let, as the scrutinee of a match, or as a freestanding expression. Surrounding context never selects which nominal the intrinsic produces; when an annotation or match is present it only checks that the intrinsic's standard Option(StrBuf) matches, and any other type — including a user-defined enum that repeats the Some/None shape under a different producer — is an ordinary type error (E0702). Because the standard Option is a toolchain guarantee, @read_line has this type even in a program that does not import std lexically; the compiler roots the trusted-module demand itself, and an absent standard library is a toolchain-integrity error rather than a language state.

@read_line reads bytes from standard input until a newline character (\n) is encountered or end-of-file is reached.

On a successful read the result is Some(line), where the line StrBuf does not include the trailing newline character.

If end-of-file is reached with some data read, the partial line is returned as Some(line).

If end-of-file is reached with no data read, the result is None (this is not an error; a read-until-end-of-input loop terminates by observing None).

If a read error occurs, a runtime panic occurs with the message "input error". If allocation or capacity growth fails while constructing the returned StrBuf, the allocation-failure rules of §8.6 apply. (These behaviors are documented but not tested here, as the failures cannot be reliably simulated through portable source-level input.)

const std = @import("std");
const Opt = std.option.Option(std.strbuf.StrBuf);

fn main() -> i32 {
    @dbg("What is your name?");
    match @read_line() {
        Opt.Some(name) => @dbg(name),
        Opt.None => @dbg("(no input)"),
    }
    0
}

Reading every line until end-of-input:

const std = @import("std");
const Opt = std.option.Option(std.strbuf.StrBuf);

fn main() -> i32 {
    loop {
        let line: Opt = @read_line();
        match line {
            Opt.None => break,
            Opt.Some(text) => @dbg(text),
        }
    }
    0
}

Integer Parsing Intrinsics

The integer parsing intrinsics convert a string to an integer value.

Each parsing intrinsic returns the trusted standard Option specialized at its target integer type T — the producer-nominal enum declared by std.option.Option (std/option.rue, ADR-0038):

  • @parse_i32 returns Option(i32)
  • @parse_i64 returns Option(i64)
  • @parse_u32 returns Option(u32)
  • @parse_u64 returns Option(u64)

The intrinsic yields this exact standard specialization in every context — bare as the operand of the ? operator (§4.15), as the initializer of an annotated let, as the scrutinee of a match, or as a freestanding expression. Surrounding context never selects the nominal; when present it only checks that the intrinsic's standard Option(T) matches, and any other type — including a user-defined Some/None lookalike under a different producer — is an ordinary type error (E0702). As with @read_line (rule 4.13:35), the compiler roots the trusted standard Option itself, so a parsing intrinsic has this type even without a lexical std import.

Each parsing intrinsic accepts exactly one argument, which MUST be one of the text types str, Str(N), or StrBuf.

The string argument is borrowed, not consumed. The original string remains valid after parsing.

A successful parse yields Some(n). The parsed string is parsed successfully when it matches the following grammar:

integer_string = [ "-" ] digit { digit } ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;

Leading minus signs are only allowed for signed types (@parse_i32, @parse_i64); a negative value for an unsigned type is a parse failure (yields None).

The result is None (a recoverable parse failure, not a panic) if:

  • The string is empty
  • The string contains non-digit characters (other than an optional leading minus)
  • The value overflows the target type
  • A negative value is parsed for an unsigned type
const std = @import("std");

fn main() -> i32 {
    let Opt = std.option.Option(i32);
    match @parse_i32("42") {
        Opt.Some(n) => n,   // returns 42
        Opt.None => 0,
    }
}
const std = @import("std");

fn main() -> i32 {
    let Opt = std.option.Option(i32);
    match @parse_i32("-17") {
        Opt.Some(n) => n,   // returns -17
        Opt.None => 0,
    }
}
const std = @import("std");

fn main() -> i32 {
    let Opt = std.option.Option(i32);
    let s = "42";
    // StrBuf is borrowed, not consumed
    let parsed: Opt = @parse_i32(s);
    @dbg(s);  // s is still valid
    match parsed {
        Opt.Some(n) => n,
        Opt.None => 0,
    }
}
// An invalid character is a recoverable failure: `None`, not a panic.
const std = @import("std");

fn main() -> i32 {
    let Opt = std.option.Option(i32);
    match @parse_i32("12abc") {
        Opt.Some(n) => n,
        Opt.None => -1,   // taken: "12abc" is not an integer
    }
}
// A negative value for an unsigned type is a recoverable failure: `None`.
const std = @import("std");

fn main() -> i32 {
    let Opt = std.option.Option(u32);
    match @parse_u32("-17") {
        Opt.Some(n) => @intCast(n),
        Opt.None => 0,   // taken: "-17" is negative
    }
}

@random_u32

The @random_u32 intrinsic generates a random unsigned 32-bit integer.

@random_u32 accepts no arguments.

The return type of @random_u32 is u32.

Each call to @random_u32 returns a non-deterministic value using a platform-provided cryptographically-secure entropy source.

If the platform entropy source is unavailable or fails, a runtime panic occurs.

fn main() -> i32 {
    let secret: u32 = (@random_u32() % 100) + 1;  // Random number 1-100
    @dbg(secret);
    0
}

Using @random_u32 in a guessing game:

const std = @import("std");
const StrBuf = std.strbuf.StrBuf;

fn main() -> i32 {
    let OptStr = std.option.Option(StrBuf);
    let OptU32 = std.option.Option(u32);
    let secret: u32 = (@random_u32() % 100) + 1;  // 1-100
    @dbg("Guess the number between 1 and 100!");

    let mut guesses = 0;
    loop {
        let input: OptStr = @read_line();
        match input {
            OptStr.None => break,          // end of input
            OptStr.Some(text) => {
                match @parse_u32(text) {
                    OptU32.Some(guess) => {
                        guesses = guesses + 1;
                        if guess < secret {
                            @dbg("Too low!");
                        } else if guess > secret {
                            @dbg("Too high!");
                        } else {
                            @dbg("You got it!");
                            break;
                        }
                    },
                    OptU32.None => @dbg("not a number, try again"),
                }
            },
        }
    }

    @intCast(guesses)
}

@random_u64

The @random_u64 intrinsic behaves identically to @random_u32 but returns a random unsigned 64-bit integer.

@random_u64 accepts no arguments.

The return type of @random_u64 is u64.

fn main() -> i32 {
    let large_random = @random_u64();
    @dbg(large_random);
    0
}

@arg_count, @arg_len, @arg_ptr

The @arg_count, @arg_len, and @arg_ptr intrinsics expose the command-line arguments the platform loader supplied to the process at entry. They are the low-level surface on which std.env builds its owned-StrBuf accessors; the argument vector is a fixed process input, so a program observes the same arguments for the whole of its execution.

@arg_count accepts no arguments and returns a u64: the number of command-line arguments, including argv[0] (the program invocation path). A process is always launched with at least one argument, so the result is at least 1.

@arg_len accepts one u64 index and returns a u64: the length in bytes of argument i, not counting any terminator. @arg_ptr accepts one u64 index and returns ptr mut u8, a pointer to the first of that argument's bytes. Because @arg_ptr yields a raw pointer, it may appear only inside a checked block (§9.2), exactly like @alloc; @arg_count and @arg_len impose no such requirement.

When the index passed to @arg_len or @arg_ptr is greater than or equal to @arg_count(), @arg_len returns 0 and @arg_ptr returns a null pointer. For an in-range index, the @arg_ptr pointer addresses exactly @arg_len(i) readable bytes. Argument bytes are not interpreted as UTF-8 (ADR-0035).

fn main() -> i32 {
    // A program invoked with no extra arguments still sees argv[0].
    @dbg(@arg_count() >= 1);            // true
    0
}

@env_count, @env_len, @env_ptr

The @env_count, @env_len, and @env_ptr intrinsics expose the process environment the platform loader supplied at entry, as a sequence of KEY=VALUE byte strings. They mirror the @arg_* intrinsics and back std.env's environment lookups.

@env_count accepts no arguments and returns a u64: the number of environment entries. @env_len accepts one u64 index and returns a u64: the length in bytes of environment entry i. @env_ptr accepts one u64 index and returns ptr mut u8, a pointer to the first of that entry's bytes; like @arg_ptr, it may appear only inside a checked block (§9.2), while @env_count and @env_len may appear in any expression position.

When the index passed to @env_len or @env_ptr is greater than or equal to @env_count(), @env_len returns 0 and @env_ptr returns a null pointer. For an in-range index, the @env_ptr pointer addresses exactly @env_len(i) readable bytes, which encode one KEY=VALUE pair. Environment bytes are not interpreted as UTF-8 (ADR-0035).

fn main() -> i32 {
    // The environment is captured once; entries can be scanned by index.
    let mut i: u64 = 0;
    while i < @env_count() {
        i = i + 1;
    }
    0
}

@target_arch

The @target_arch intrinsic returns the target architecture as an Arch enum value.

@target_arch accepts no arguments.

The return type of @target_arch is Arch.

The Arch enum is a built-in enum with the following variants:

  • Arch.X86_64 - x86-64 architecture
  • Arch.Aarch64 - ARM64/AArch64 architecture

The value returned by @target_arch is determined at compile time based on the compilation target.

fn main() -> i32 {
    match @target_arch() {
        Arch.X86_64 => 1,
        Arch.Aarch64 => 2,
    }
}

@target_os

The @target_os intrinsic returns the target operating system as an Os enum value.

@target_os accepts no arguments.

The return type of @target_os is Os.

The Os enum is a built-in enum with the following variants:

  • Os.Linux - Linux operating system
  • Os.Macos - macOS operating system

The value returned by @target_os is determined at compile time based on the compilation target.

fn main() -> i32 {
    match @target_os() {
        Os.Linux => 1,
        Os.Macos => 2,
    }
}

Combining @target_arch and @target_os for platform-specific code:

fn main() -> i32 {
    match @target_arch() {
        Arch.X86_64 => {
            match @target_os() {
                Os.Linux => 99,
                Os.Macos => 88,
            }
        },
        Arch.Aarch64 => {
            match @target_os() {
                Os.Linux => 77,
                Os.Macos => 66,
            }
        },
    }
}

@target_data_model

The @target_data_model intrinsic returns the compilation target's C data model — the width convention the target's C ABI assigns to int, long, and pointers, which selects the widths of the std.c transparent scalar aliases (ADR-0064 Amendment 1) — as a DataModel enum value.

@target_data_model accepts no arguments.

The return type of @target_data_model is DataModel.

The DataModel enum is a built-in enum with the following variants:

  • DataModel.Ilp32 - int, long, and pointers are 32-bit
  • DataModel.Llp64 - long long and pointers are 64-bit; long remains 32-bit
  • DataModel.Lp64 - long and pointers are 64-bit

The value returned by @target_data_model is determined at compile time based on the compilation target. Every target the reference compiler currently supports (the x86-64 and AArch64 Linux/macOS targets reachable through @target_arch/@target_os) is Lp64.

fn main() -> i32 {
    match @target_data_model() {
        DataModel.Lp64 => 1,
        DataModel.Llp64 => 2,
        DataModel.Ilp32 => 3,
    }
}

@import

The @import intrinsic imports a module from another source file.

@import accepts exactly one argument, which MUST be a string literal specifying the module path.

The return type of @import is a module struct type containing all pub declarations from the imported file.

Module path resolution follows this order:

  1. Standard library: @import("std") resolves to the bundled standard library
  2. A file {path}.rue relative to the importing file's directory
  3. A directory module: a directory {path}/ containing the facade file _{basename}.rue, which is the module's root

It is a compile-time error if the module path does not resolve to an existing file.

It is a compile-time error to pass a non-string-literal argument to @import.

// math.rue
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn sub(a: i32, b: i32) -> i32 { a - b }
fn helper() -> i32 { 42 }  // private, not exported

// main.rue
fn main() -> i32 {
    let math = @import("math.rue");
    math.add(1, 2)  // returns 3
}

Private declarations (those without pub) are not visible to importers:

// main.rue
fn main() -> i32 {
    let math = @import("math.rue");
    // math.helper()  // Error: `helper` is not visible
    0
}

The imported module can be bound to any name:

fn main() -> i32 {
    let m = @import("math.rue");
    m.add(1, 2)
}

Nested paths are supported for importing from subdirectories:

fn main() -> i32 {
    let strings = @import("utils/strings.rue");
    0
}

It is a compile-time error (E0713) if a relative import path's normalized candidate falls outside the project root (the root file's directory); see rule 10.2:7. Module identity is project-root-relative, so a file outside the root can receive no identity.

It is a compile-time error to access a member of a module that is not declared in the imported file. Module membership is per-file: even though all compiled files share one global function namespace, a declaration from some other file is not a member of the module and MUST NOT be reachable through it.

@wrapping_add, @wrapping_sub, @wrapping_mul

The @wrapping_add, @wrapping_sub, and @wrapping_mul intrinsics each take exactly two operands and compute, respectively, the sum, difference, and product of those operands with two's-complement wraparound instead of the overflow trap of the +, -, and * operators. Each is a non-trapping counterpart to the corresponding checked operator.

Both operands and the result of a wrapping-arithmetic intrinsic share a single integer type, established by the same equality-and-integer constraints as the checked +, -, and * operators (§4.2). It is a compile-time error to apply one to a non-integer operand, and it is a compile-time error for the two operands to have differing integer types.

The intrinsics are defined for every integer type — the signed widths i8, i16, i32, i64 and the unsigned widths u8, u16, u32, u64. For a result type of width N bits, the value of the intrinsic is the true mathematical result reduced modulo 2^N and interpreted as a two's-complement value of the result type. Equivalently, the result is congruent to the exact mathematical result modulo 2^N and lies within the range of the result type.

A wrapping-arithmetic intrinsic never traps: for every combination of operand values, including those for which the checked operator would trap on overflow, it produces the reduced value defined by 4.13:93. Because the low N bits of a two's-complement product do not depend on the signedness of the operands, @wrapping_mul yields the same bit pattern for a signed and an unsigned type of the same width.

In a compile-time context, a wrapping-arithmetic intrinsic is evaluated exactly as @intCast is: the intrinsics introduce no new constant-evaluation rule, so an invocation that @intCast would not fold into a constant is likewise not a constant expression.

fn main() -> i32 {
    // 127 + 1 wraps to the minimum i8 value.
    let hi: i8 = 127;
    let one: i8 = 1;
    @dbg(@wrapping_add(hi, one));      // -128

    // 0 - 1 wraps to the maximum u8 value.
    let zero: u8 = 0;
    let uone: u8 = 1;
    @dbg(@wrapping_sub(zero, uone));   // 255

    // The FNV-1a hashing step that overflows u64.
    let hash: u64 = 14695981039346656037;
    let prime: u64 = 1099511628211;
    @dbg(@wrapping_mul(hash, prime));  // 12638153115695167455

    0
}