Compile time · Lesson 23.7

Intrinsic

Source
See that int8::Max, #target and Assert reach a program through an extension and 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 type int8 is built into the compiler, but its constants come from an extension, and #target and Assert from declarations of what the compiler implements — the fields of #target, the code an Assert turns into. Core is simply the package that usually provides both.

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.

An extension and three kinds of intrinsic

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

Constants on a built-in type. int8, like every primitive type, is built into the compiler, so nothing declares it. The constants on it are ordinary source in an extension, so the provider decides which ones to offer:

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
extend int8the 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; importing int8 and float64 imports Basis's extensions of them:

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, and import Basis::int16; is refused because Basis does not extend 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 function called Square, a wordSize field on Target, an Assert whose condition is an int. A type cannot be declared at all, since every primitive is built in. 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 type `int8` is built in, but its constants come from an
// extension, and `#target` and `Assert` from *declarations*; `Core` is simply the package that
// usually provides both. 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. An `intrinsic func` the compiler does not
// implement "is not a supported intrinsic function", and a type cannot be declared at all: every
// primitive is built in, so `intrinsic type int7` is refused because "intrinsic type declarations
// have been removed". 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

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 int8; to Basis, read the error and its help, 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