Skip to content

Compile-time features

Beans folds a few things to constants at compile time, always for the selected target (what --target picks, or the host by default). Because both the native backend and the interpreter read the same folded numbers, they can never disagree.

Three forms answer layout questions about a type:

let bytes: int = size_of(Packet)
let step: int = align_of([f32; 4])
let word: int = size_of(RawPtr<Packet>)
let at: int = offset_of(Packet, count)
  • size_of(T), align_of(T), and offset_of(T, field) are contextual forms that take a type. Each name has this meaning only immediately before (, so the same words stay usable as ordinary identifiers elsewhere.
  • The values are compile-time constants of the selected target. beansc build --target X reports X’s layout, not the host’s.
  • Supported types: integers, floats, bool, decimal, string, RawPtr<T>, Slice<T>, SIMD values, fixed arrays (nested included), struct and extern "C" struct/union, and class or interface references (a reference is one pointer).
  • Rejected, with a specific message: a type parameter (size_of(T) inside a generic body), and Option/Result/user enums. Those pick between a null niche, an inline aggregate, and a boxed form depending on payload, so there is no single number to report.
  • offset_of needs a struct/union and a real field name.

For extern "C" records these numbers match C’s sizeof/alignof/offsetof, verified against Clang.

std.target reads the selected target’s facts as compile-time constants:

import std.io
import std.target
fn main() {
io.println(target.triple()) // "arm64-apple-darwin"
io.println("{target.pointer_bits()}") // 64
}

Strings: triple, arch, os, env, object_format, endian. Ints: pointer_bits, pointer_size, stack_align, max_simd_bits. Under beansc run the selected target is always the host. max_simd_bits follows --cpu and --features. See std.target.

cpu.has(CpuFeature.x) asks the machine that is running, so you can pick a faster path only when the hardware supports it:

import std.cpu
feature "aes" fn mix_fast(seed: int) -> int { /* ... */ }
fn mix_generic(seed: int) -> int { /* ... */ }
fn mix(seed: int) -> int {
if cpu.has(CpuFeature.aes) { return mix_fast(seed) }
return mix_generic(seed)
}
  • The feature name is validated against the selected target’s feature set, so asking about avx2 while targeting arm64 is a compile error, not a permanent false.
  • CpuFeature is neither a declarable type nor a storable value. Like a memory order, it is written at the call site.
  • feature "x" fn marks a body as allowed to use that feature’s instructions. Calling it (or storing it as a function value) requires the feature to be known present: inside a matching if cpu.has(...) guard, from another function that requires the same feature, or in a build given --features +x.
  • x86 spells sse4.1/sse4.2 with an underscore in CpuFeature: CpuFeature.sse4_2. The string forms (--features, feature "x" fn) keep the dot.

The full operation set is in std.cpu and std.intrinsic. The attributes and modifiers page lists feature alongside the other declaration modifiers.

Annotation arguments are also checked at compile time. They accept constant booleans, numbers, strings, enum variants, and lists, but not calls or reads of runtime values.