Skip to content
CryoCryo home
LanguageProgram structure

17Directives and Attributes

Directives are compile-time annotations attached to declarations using ![...] syntax. They modify how the compiler treats the declaration without changing its semantic meaning at the call site.

![inline]
function hot_path(x: int) -> int {
    return x * 2;
}

![repr(packed)]
type struct PackedHeader {
    magic:   u32;
    version: u8;
    flags:   u8;
}

![align(16)]
type struct AlignedData {
    data: f64;
}

17.1 Recognised Attributes

DirectiveTargetDescription
![inline]functionInlining hint. Parsed and validated; not yet emitted as an LLVM attribute (no effect at the default -O0).
![noinline]functionAnti-inlining hint. Parsed and validated; not yet emitted as an LLVM attribute.
![deprecated] / ![deprecated("msg")]any declMarks a declaration as deprecated. Parsed and validated; use-site warnings are not yet emitted.
![symbol("name")]extern fn / method / fn definitionOverride the linker symbol: Cryo callers use the declared name while the symbol emitted / resolved at link time is name. On an extern decl or body-less method it renames the imported symbol; on a function definition it renames the emitted symbol. Used by the C++ binding generator to pin a decl to its Itanium-mangled symbol.
![no_mangle]function / methodEmit the definition under its bare declared identifier instead of the mangled C$… symbol, so it can be linked from C (or serve as a runtime lang-item) without a wrapper. The preferred unmangling directive; ![symbol("name")] is for the rename case.
![weak]function / methodEmit the definition with weak linkage (and an any-selection COMDAT), so a strong definition elsewhere overrides it. For user-overridable runtime symbols (allocator, panic hook).
![section("name")]function / methodPlace the emitted symbol in object-file section name.
![naked]functionEmit no prologue/epilogue (sets LLVM's naked attribute); the body must be a single asm { … } block that provides its own ret/jmp/exit. For a freestanding _start and other raw entry points. A non-asm body is an error (E0151).
![allow(name)] / ![warn(name)] / ![deny(name)]any declIntended to adjust a named lint's level. Parsed and validated; lint-level adjustment is not yet implemented.
![derive(Trait, ...)]struct / class / enumIntended to auto-derive one or more traits. Parsed and validated; no trait is actually synthesized. See section 22.
![sink]methodMarks a method as consuming its receiver, even when the receiver is syntactically &this or mut &this. Useful for methods that semantically take ownership but want the borrow-style call ergonomics.
![implicit]generic function / methodLets a call omit its generic type arguments: the compiler recovers them by unifying the declared return type against the call's expected type (the type of the const x: T = ... it initialises). Every type parameter must appear in the return type. A call with no expected type reports E0307; write the arguments explicitly (f<T>(...)) there. Used by VaArgs::next so const n: i64 = va.next() reads cleanly.
![config(<atom>)] / ![target(<atom>)] / ![<atom>]any declPlatform / build-flavor gate. <atom> is windows, linux, macos, unix, or not(<atom>). The bare-atom form (![windows]) is sugar for ![config(windows)]. The decl is stripped from the AST when the gate doesn't match.
![repr(C)] / ![repr(packed)] / ![repr(transparent)]struct / class / enumMemory layout control. See section 17.3.
![align(N)]struct / class / variableMinimum alignment in bytes; N must be a power of two. See section 17.3.
![arch(<arch>, <dialect>)]asm blockRequired directly above an asm { ... } block (see section 6.13). Names the target architecture - which gates the block, like ![config] - and the assembly dialect (intel or att).
![clobber(...)]asm blockLists registers, flags, or memory that an asm block overwrites but does not bind as operands.

The test-mode directives (![config(testing)], ![test], ![ignore], ![should_panic]) are documented in the next subsection.

Other names that appear with ![...] syntax are accepted by the parser and recorded so user tooling can observe them, but the compiler emits a warning for any non-builtin name and applies no semantics. A list of reserved-but-unimplemented directive names is in section 22.

17.2 Test Directives

The test framework introduces a small set of directives recognised by the compiler (section 21 covers usage):

DirectiveDescription
![config(testing)]Marks a file as a test source. May appear next to its namespace.
![test]Marks a function as a discoverable test.
![ignore]Skips the test unless cryo test --ignored is passed.
![should_panic]Asserts the test panics; passing the test means observing a panic.

17.3 Memory Layout

Cryo is a systems language and gives programmers explicit control over the in-memory layout of user-defined types. Layout matters for FFI (matching a struct declared in a C header), for binary protocols (network packets, on-disk records, hardware register maps), and for memory-conscious data structures.

17.3.1 The Default Layout (repr(Cryo))

Without any explicit directive, a struct or class has the default Cryo layout:

  • Fields are laid out in source order.
  • Each field is placed at the lowest offset that satisfies its natural alignment.
  • The size of the type is rounded up to the type's alignment (the maximum alignment among its fields, or 1 for empty types).
  • Padding bytes inserted to satisfy alignment have undefined content.

The default layout currently matches the platform's C ABI for primitive-typed fields. The language reserves the right to optimise the default layout in future versions (e.g. reordering fields). Do not rely on the default layout for FFI or binary serialisation; use ![repr(C)] for those cases.

A class with virtual methods carries an 8-byte vtable pointer at offset 0; non-virtual classes have no vtable. Inheritance is flattened in root-to-derived order so an upcast pointer is a no-op.

17.3.2 ![repr(C)]

![repr(C)]
type struct timespec {
    tv_sec:  i64;
    tv_nsec: i64;
}

![repr(C)] guarantees that the type's layout matches the platform's C ABI:

  • Fields are laid out in source order.
  • Padding follows the C rules: each field starts at the lowest offset that is a multiple of its alignment.
  • The size of the type is rounded up to the type's alignment.
  • The compiler will not reorder fields under any circumstances.

![repr(C)] is the correct choice for any type that is exchanged with C code, mapped to a C header, or used as a binary record. Combine with ![align(N)] to over-align such a struct.

17.3.3 ![repr(packed)]

![repr(packed)]
type struct PacketHeader {
    version: u8;
    flags:   u8;
    length:  u32;     // no padding before this field
    crc:     u32;
}

![repr(packed)] removes all inter-field padding:

  • Fields are laid out in source order.
  • Each field is placed at the next byte offset, regardless of its natural alignment.
  • The type's alignment is 1.
  • The size of the type equals the sum of its fields' sizes.

A repr(packed) type may contain misaligned fields. Taking a Cryo reference (& or mut &) to a field of a repr(packed) type is a compile error: the reference would not be naturally aligned for its referent, which is undefined behaviour. Read or write the field by value instead; the compiler emits the unaligned load/store at the use site.

![repr(packed)] and ![align(N)] are mutually exclusive on the same type.

17.3.4 ![repr(transparent)]

![repr(transparent)]
type struct Pid {
    inner: i32;
}

![repr(transparent)] declares a wrapper type whose layout is identical to a single contained field:

  • The type must have exactly one field whose size is non-zero.
  • The wrapper has the same size, alignment, and ABI as that inner field.
  • A Pid and an i32 are interchangeable at the ABI level - passing Pid to an extern "C" function is identical to passing i32.

This is the recommended idiom for type-safe wrappers around primitive FFI types (file descriptors, error codes, opaque handles) where the wrapper exists purely for type discipline at the source level.

17.3.5 ![align(N)]

![align(64)]
type struct CacheLine {
    payload: u8[64];
}

![align(N)] sets the minimum alignment of the type to N bytes:

  • N must be a power of two between 1 and 65536.
  • The actual alignment of the type is max(natural_alignment, N).
  • The size of the type is rounded up to its alignment.

![align(N)] is compatible with ![repr(C)] and ![repr(transparent)]; it is mutually exclusive with ![repr(packed)].

![align(N)] on a variable raises that variable's alignment for the lifetime of its storage; this is useful for stack buffers that must satisfy SIMD or hardware-register alignment requirements.

17.3.6 Enums

A unit enum (no variant payloads) has a discriminant whose type defaults to i32. The discriminant type can be set explicitly with the type-annotation syntax (not a directive):

type enum Color : u8 {
    Red    = 0;
    Green  = 1;
    Blue   = 2;
}

An ADT enum (variants with payloads) is laid out as a tag (i32) followed by a payload area sized to the largest variant, with padding so the payload area is naturally aligned. ![repr(C)] on an ADT enum is reserved syntax and currently produces the same layout as the default.

17.3.7 Inspecting Layout: sizeof(T) and alignof(T)

sizeof(T) and alignof(T) return compile-time u64 constants reflecting the type's chosen layout - including any ![repr] or ![align] directives applied to it. They are the recommended way to verify FFI struct layout against a C header in a test:

![test]
function timespec_matches_c() -> void {
    expect_eq(sizeof(timespec),  16);
    expect_eq(alignof(timespec), 8);
}