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
| Directive | Target | Description |
|---|---|---|
![inline] | function | Inlining hint. Parsed and validated; not yet emitted as an LLVM attribute (no effect at the default -O0). |
![noinline] | function | Anti-inlining hint. Parsed and validated; not yet emitted as an LLVM attribute. |
![deprecated] / ![deprecated("msg")] | any decl | Marks a declaration as deprecated. Parsed and validated; use-site warnings are not yet emitted. |
![symbol("name")] | extern fn / method / fn definition | Override 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 / method | Emit 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 / method | Emit 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 / method | Place the emitted symbol in object-file section name. |
![naked] | function | Emit 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 decl | Intended to adjust a named lint's level. Parsed and validated; lint-level adjustment is not yet implemented. |
![derive(Trait, ...)] | struct / class / enum | Intended to auto-derive one or more traits. Parsed and validated; no trait is actually synthesized. See section 22. |
![sink] | method | Marks 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 / method | Lets 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 decl | Platform / 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 / enum | Memory layout control. See section 17.3. |
![align(N)] | struct / class / variable | Minimum alignment in bytes; N must be a power of two. See section 17.3. |
![arch(<arch>, <dialect>)] | asm block | Required 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 block | Lists 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):
| Directive | Description |
|---|---|
![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
Pidand ani32are interchangeable at the ABI level - passingPidto anextern "C"function is identical to passingi32.
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:
Nmust 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);
}