Intrinsic
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.
| Kind | The compiler supplies | The provider writes |
|---|---|---|
intrinsic type | the type and its operations | constants and methods, as plain source |
intrinsic const | the value | the name and the type |
intrinsic #name | each field's value | the struct, with the fields it exposes |
intrinsic func | the code at every call | the 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"| pSince 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.
// `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
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.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.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.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
- Add
pub triple: char8[..];to theTargetstruct inBasisand print#target.triplefromMain. - Change
levelto127and run. Which line stops the program, and with what message? - Add
pub intrinsic type int7;toBasis, read the error, and take it out again. - Print
int16::MaxfromMain. Then add the constants toBasis, the wayint8has them, until it builds.
Learn more
- Intrinsics and Intrinsic constants in the Rux Reference
- The Core package — the provider every other lesson uses
- Dependency and Source library — how
Basisreaches the program - Target — the full
#targetthatCoredeclares
23.6 Define
Hand a build your own named values with --define, and read them while compiling with #config.Has and #config.Get.
Overview
Step outside Rux to the operating system and the processor. Five lessons on extern and #Link, the C runtime and its types, calling conventions with #Abi, and function bodies written in x86-64 and AArch64 assembly.