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 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.
| Kind | The compiler supplies | The provider writes |
|---|---|---|
extend int8 | 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; 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"| 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, 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.
// `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
- 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 int8;toBasis, read the error and its help, 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.