Intrinsics
An intrinsic is something the compiler implements itself: the value of float64::Infinity, the fields of #target, the code an Assert turns into. A program reaches it only through an intrinsic declaration, which names it and gives its type, with no body. The Core package holds the standard declarations, and every program imports them from there — import Core::{ #target, Assert, float64 }; — but Core has no special status: any package may declare the same intrinsics and be imported instead.
intrinsic-decl = [ "pub" ] "intrinsic" ( "const" identifier ":" type ";"
| "#" identifier ":" type ";"
| function-signature ";" )
intrinsic const is written inside an extend block, and intrinsic func may be too, where it declares a method.
Three kinds
| Declaration | The compiler supplies | The provider writes |
|---|---|---|
intrinsic const Infinity: float64; | the value | the name and type, in an extend block |
intrinsic #target: Target; | each field the struct declares | the struct, with the fields it exposes |
intrinsic func Assert(…); | the code at every call | the exact signature the compiler expects |
A provider package, declaring a little of each beside the constants it adds to built-in types:
// A built-in type needs no declaration. Its constants are ordinary source in an extension.
extend int8 {
pub const Min: int8 = -128i8;
pub const Max: int8 = 127i8;
}
// A constant no literal can spell.
extend float64 {
pub intrinsic const Infinity: float64;
}
// A compiler-supplied value, exposing only the fields this provider chooses.
pub struct Target {
pub pointerBits: uint;
}
pub intrinsic #target: Target;
// Functions the compiler emits at each call.
pub intrinsic func Assert(condition: bool, message: char8[..]);
pub intrinsic func #Error(message: char8[..]);
A program that depends on it imports the same names it would otherwise take from Core, and works the same way. Importing int8 and float64 imports the provider's extensions of those built-in types:
import Basis::{ #target, #Error, Assert, float64, int8 };
func Main() -> int {
when #target.pointerBits < 64 {
#Error("needs a 64-bit target");
}
Assert(int8::Max == 127i8, "the provider's constant");
Assert(float64::Infinity > 1.0e308, "a compiler-supplied constant");
return 0;
}
Only what the provider declares is available: with Basis alone, int16 still exists as a type, but int16::Max does not, and import Basis::int16; is refused because Basis does not extend it.
Primitive types are not intrinsics
Every primitive type is built into the compiler — int8 … int512, uint8 … uint512, int, uint, float8 … float512, bool8 … bool512, char8 … char512, and the aliases bool, byte, char and float — so no package declares one, and intrinsic type no longer exists:
error: intrinsic type declarations have been removed; primitive types are built in
help: declare its associated constants in 'extend int8 { ... }' instead
A primitive's representation exists everywhere; an extension supplies its API. That is why int8::Max needs import Core::int8; while the type int8 does not — see Imports.
Constants
Associated constants are normally plain source (pub const Max: int8 = 127i8;). The only compiler-supplied ones are the floating-point limits Max, Lowest, MinPositive, Epsilon, Infinity and NaN, at every width from float8 to float512. Each is declared without an initializer in an extend of its own type, and has that type:
extend float128 {
pub intrinsic const Max: float128;
pub intrinsic const Epsilon: float128;
}
Max is the largest finite value, Lowest its negation, MinPositive the smallest positive normal value, Epsilon the gap between one and the next value up, and NaN a quiet NaN. Core declares Infinity and NaN this way at every width, and all six for float80 and wider, whose limits are too long to be worth spelling in decimal; it writes the finite limits of float8 through float64 as literals. Any other name, or a type other than the extended one, is refused:
error: intrinsic associated constants support only floating-point Infinity, NaN, Max, Lowest, MinPositive and Epsilon
error: intrinsic associated constant must have its owning floating-point type
A when condition computes with floats as float64, so it can read Infinity and NaN at every width, but not the intrinsic finite limits of float80 and wider.
Compile-time values
intrinsic #name: Root; declares a value the compiler fills in. The type names which value it is — one of the structs Target, Build, Source, Compiler or Config — and the # name is how code refers to it. Core uses #target, #build, #source, #compiler and #config.
The struct may declare any subset of the fields the compiler supplies for its root, and nothing else, since nothing would fill a field it does not know:
error: field 'wordSize' of 'Target' is not one the compiler supplies for '#target'
help: remove 'wordSize'; the compiler supplies 'os', 'arch', 'abi', 'endian', 'pointerBits', 'dataModel', 'objectFormat', 'triple'
error: 'Widget' is not a value the compiler supplies for '#thing'
help: declare '#thing' with one of 'Target', 'Build', 'Source', 'Compiler', 'Config'
The fields themselves are listed in Context. Field visibility is ordinary: a field the provider leaves private cannot be read by another package, in an expression or in a when condition.
Functions
intrinsic func names a function the compiler implements, so only those it knows are accepted, each with exactly one signature:
| Intrinsic | Signature | Purpose |
|---|---|---|
Assert | (condition: bool, message: char8[..]) | stop the program unless condition holds |
DebugAssert | (condition: bool, message: char8[..]) | the same, removed without debug assertions |
Panic | (message: char8[..]) | stop the program |
#Error | (message: char8[..]) | stop the build — see Diagnostics |
#Warn | (message: char8[..]) | warn during the build |
CheckedAdd, CheckedSub, CheckedMul | (left: uint64, right: uint64, result: *var uint64) -> bool | arithmetic that reports overflow (true on overflow) |
Zeroize | (memory: *var uint8, length: uint64) | a clearing write the optimizer may not remove |
Target.HasFeature | method (self: &Target, feature: TargetFeature) -> bool | #target.HasFeature |
Compiler.HasFeature | method (self: &Compiler, feature: char8[..]) -> bool | #compiler.HasFeature |
Config.Get, Config.Has | methods (self: &Config, name: char8[..]) -> char8[..] / -> bool | #config |
A method intrinsic is declared inside an extend of its struct, which is what gives it its Type.Name key:
extend Target {
pub intrinsic func HasFeature(self: &Target, feature: TargetFeature) -> bool;
}
The diagnostic intrinsics are checked against their signatures, because the compiler emits the call in its own shape:
error: 'Square' is not a supported intrinsic function
help: give the function a body, or declare a foreign function in an 'extern' block
error: intrinsic 'Assert' must be declared as 'func Assert(condition: bool, message: char8[..])'
note: parameter 'condition' has type 'int'
error: 'intrinsic' function cannot have a body
Not intrinsics
Slices, ranges, optionals, fallibles and sums are native types with no declaration at all. intrinsic struct does not exist (error: intrinsic aggregate declarations have been removed; use native slice or range types), and an ordinary struct never gains compiler-owned behaviour from its name. An intrinsic declaration can only bind what the compiler already implements; it cannot add a feature.
See also
- Context — the fields of the compile-time values
- Imports — importing
#names and primitive constants - Primitive types — the built-in types and the constants
Coreextends them with - API:
Assert,Panic· Learn: Intrinsic, Checked arithmetic, Zeroize