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 table provides a quick reference to all available intrinsics:

IntrinsicPurposeArgumentsReturn Type
@dbgPrint debug output1 expression (int, bool, or string)()
@size_ofGet type size in bytes1 typei32
@align_ofGet type alignment in bytes1 typei32
@intCastConvert between integer types1 expression (integer)inferred integer type
@read_lineRead line from stdinnoneString
@parse_i32Parse string to i321 expression (String)i32
@parse_i64Parse string to i641 expression (String)i64
@parse_u32Parse string to u321 expression (String)u32
@parse_u64Parse string to u641 expression (String)u64
@random_u32Generate random u32noneu32
@random_u64Generate random u64noneu64
@target_archGet target architecturenoneArch
@target_osGet target OSnoneOs
@importImport module1 expression (string literal)module type

@dbg

The @dbg intrinsic prints a value to standard output for debugging purposes.

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

@dbg prints the value followed by a newline character.

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)     // 8 (one 8-byte slot)
}
struct Point { x: i32, y: i32 }

fn main() -> i32 {
    @size_of(Point)   // 16 (two 8-byte slots)
}

@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.

All types in Rue currently have 8-byte alignment.

fn main() -> i32 {
    @align_of(i32)    // 8
}

@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
}

@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 String.

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

The returned String does not include the trailing newline character.

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

If end-of-file is reached with no data read, a runtime panic occurs with the message "unexpected end of input".

If a read error occurs, a runtime panic occurs with the message "input error". (This behavior is documented but not tested, as I/O errors cannot be reliably simulated in portable test environments.)

fn main() -> i32 {
    @dbg("What is your name?");
    let name = @read_line();
    @dbg("Hello, ");
    @dbg(name);
    0
}

Reading multiple lines:

fn main() -> i32 {
    let line1 = @read_line();  // First line
    let line2 = @read_line();  // Second line
    @dbg(line1);
    @dbg(line2);
    0
}

Integer Parsing Intrinsics

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

The following parsing intrinsics are available:

  • @parse_i32 returns i32
  • @parse_i64 returns i64
  • @parse_u32 returns u32
  • @parse_u64 returns u64

Each parsing intrinsic accepts exactly one argument, which MUST be of type String.

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

The parsed string must match 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 runtime panic occurs 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
fn main() -> i32 {
    let s = "42";
    let n = @parse_i32(s);
    n  // returns 42
}
fn main() -> i32 {
    let s = "-17";
    let n = @parse_i32(s);
    n  // returns -17
}
fn main() -> i32 {
    let s = "42";
    // String is borrowed, not consumed
    let n = @parse_i32(s);
    @dbg(s);  // s is still valid
    n
}
// This panics at runtime: invalid character
fn main() -> i32 {
    let s = "12abc";
    let n = @parse_i32(s);  // panic: invalid character
    n
}
// This panics at runtime: negative for unsigned
fn main() -> i32 {
    let s = "-17";
    let n: u32 = @parse_u32(s);  // panic: negative value for unsigned type
    @intCast(n)
}

@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:

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

    let mut guesses = 0;
    loop {
        let input = @read_line();
        let guess = @parse_u32(input);
        guesses = guesses + 1;

        if guess < secret {
            @dbg("Too low!");
        } else if guess > secret {
            @dbg("Too high!");
        } else {
            @dbg("You got it!");
            break;
        }
    }

    @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
}

@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,
            }
        },
    }
}

@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 _{path}.rue with subdirectory {path}/

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");
    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");
    // math.helper()  // Error: `helper` is not visible
    0
}

The imported module can be bound to any name:

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

Nested paths are supported for importing from subdirectories:

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