Compile time · Lesson 23.7

Intrinsic

Source
See that int8, #target and Assert reach a program through intrinsic declarations, by taking them from a small provider package of your own instead of Core.
You'll need: Dependency, Target, Assert

int8::Max, #target and Assert have come from Core in every lesson so far, so it is easy to think of them as part of the language itself. They are not. The compiler knows how to implement them — the arithmetic of an int8, the fields of #target, the code an Assert turns into — but a program reaches them only through declarations, and Core is simply the package that usually provides those declarations.

An intrinsic declaration names something the compiler supplies and gives its type, with no body. This lesson writes a few of them in Basis, a small provider package of its own, and runs a program that takes everything from Basis instead of Core. Nothing changes: the program works exactly as it would with Core.

Four kinds of intrinsic

Open Basis/Src/Basis.rux in the code tree below. It is a handful of lines, and each part declares a different kind of intrinsic.

A type. int8 is a type only the compiler can implement, so the provider just declares it. The constants on it are ordinary source, though, so the provider decides which ones to offer:

pub intrinsic type int8;

extend int8 {
    pub const Min: int8 = -128i8;
    pub const Max: int8 = 127i8;
}

A constant whose value no literal can spell. There is no way to write infinity in source, so the compiler supplies it:

extend float64 {
    pub intrinsic const Infinity: float64;
}

A compile-time value. #target is declared as an ordinary struct plus one intrinsic line that says "the compiler fills this in". The provider lists only the fields it wants to expose:

pub struct Target {
    pub pointerBits: uint;
}

pub intrinsic #target: Target;

A function the compiler emits inline. The provider writes the signature, which must be the one the compiler expects:

pub intrinsic func Assert(condition: bool, message: char8[..]);

Core declares all of these in exactly the same way — its own Assert is that same line, word for word. The package gets no special powers from being called Core.

KindThe compiler suppliesThe provider writes
intrinsic typethe type and its operationsconstants and methods, as plain source
intrinsic constthe valuethe name and the type
intrinsic #nameeach field's valuethe struct, with the fields it exposes
intrinsic functhe code at every callthe signature the compiler expects

Taking them from a provider

Basis is a source library in a folder beside the program, and the program's Rux.toml names it with a path dependency: Basis = { Path = "Basis" }. There is no Core in that manifest at all. The program imports from Basis the same names it would otherwise import from Core:

import Basis::{ #target, Assert, float64, int8 };
flowchart LR
    c(["The compiler implements<br/>int8, Infinity, #target, Assert"]) --> core["Core declares them<br/>(the usual provider)"]
    c --> basis["Basis declares them<br/>(this lesson's provider)"]
    core -.->|"every other lesson"| p["Main.rux imports<br/>the declarations"]
    basis -->|"this lesson"| p

Since only Basis is imported, only what Basis declares is available. The type int16 still exists, but int16::Max does not: no provider in this program offers it.

Only real intrinsics

intrinsic cannot invent new features. Every declaration is checked against what the compiler really implements, and anything else is refused: a type called int7, a function called Square, a wordSize field on Target, an Assert whose condition is an int. Writing a provider is a way to replace Core, not a way to extend the language.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// `int8::Max`, `#target` and `Assert` have always come from `Core`, so they can look like part of
// the language. They are not. The compiler knows how to implement them, but a program reaches them
// only through *declarations*, and `Core` is simply the package that usually provides those
// declarations. An `intrinsic` declaration names something the compiler supplies and gives its
// type, with no body.
//
// This lesson takes the declarations from `Basis`, a small provider package beside this one,
// instead of from `Core`. Open `Basis/Src/Basis.rux`: it is a handful of lines, and the program
// below works the same as it would with `Core`.
//
// Only real intrinsics can be declared this way. `intrinsic type int7` is refused because "'int7'
// is not a supported intrinsic scalar type", and an `intrinsic func` the compiler does not
// implement "is not a supported intrinsic function". Writing a provider is a way to replace
// `Core`, not a way to invent new language features.
import Basis::{ #target, Assert, float64, int8 };
import Io::PrintLine;

func Main() -> int {
    PrintLine("int8 runs from {} to {}", int8::Min, int8::Max);
    PrintLine("float64 can hold {}", float64::Infinity);
    PrintLine("Pointers on this target are {} bits wide", #target.pointerBits);

    let level: int8 = 100;
    Assert(level < int8::Max, "the level must leave room to grow");
    PrintLine("Level {} passed the check", level);
    return 0;
}

Run it

cd Examples/CompileTime/Intrinsic
rux run
int8 runs from -128 to 127
float64 can hold Inf
Pointers on this target are 64 bits wide
Level 100 passed the check

The provider is the companion package in Basis/.

Common mistakes

Declaring a type the compiler does not have.
pub intrinsic type int7; fails with error: 'int7' is not a supported intrinsic scalar type. Likewise, an intrinsic func the compiler does not implement fails with error: 'Square' is not a supported intrinsic function.
Adding a field the compiler does not fill in.
A wordSize field on Target fails with error: field 'wordSize' of 'Target' is not one the compiler supplies for '#target'. Every field of an intrinsic struct must be one the compiler knows.
Getting an intrinsic signature wrong.
Declared with condition: int, Assert fails with error: intrinsic 'Assert' must be declared as 'func Assert(condition: bool, message: char8[..])'. The message tells you the one signature it accepts.
Using a constant the provider does not offer.
Only what Basis declares is in scope. int16::Max fails with error: 'Max' not found in extend for type 'int16', because Basis declares no constants for int16.

Try it yourself

  1. Add pub triple: char8[..]; to the Target struct in Basis and print #target.triple from Main.
  2. Change level to 127 and run. Which line stops the program, and with what message?
  3. Add pub intrinsic type int7; to Basis, read the error, and take it out again.
  4. Print int16::Max from Main. Then add the constants to Basis, the way int8 has them, until it builds.

Learn more