6.2. Built-in attributes
This section defines the built-in attributes the compiler processes directly. #[cfg] is covered separately in conditional compilation, and magic attributes and meta blocks are described in the sections from #[lang(...)] magic onward.
derive_attr ::= "#" "[" "derive" "(" trait_name ("," trait_name)* ","? ")" "]"
trait_name ::= identifier
repr_attr ::= "#" "[" "repr" "(" repr_arg ("," repr_arg)* ")" "]"
repr_arg ::= "C" | "transparent" | "packed" | "align" "(" int_lit ")"
| identifier // primitive int repr
inline_attr ::= "#" "[" "inline" ("(" ("always" | "never") ")")? "]"
deprecated_attr ::= "#" "[" "deprecated" ("(" string_lit ")")? "]"
6.2.1. #[derive(...)] automatic trait implementation
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct Point { x: i64; y: i64; }
The arguments of #[derive(...)] are trait names separated by commas. For each listed trait, an automatic implementation suited to the type is generated.
Built-in derivable traits
Only Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, and Hash can be derived. The generation rules and requirements for each trait are defined in stdlib 2.13.11.
Displaycannot be derived. The display format is decided by the type's author (stdlib 2.10).- Deriving
Eq/Ordwithout their supertraits (PartialEq/PartialOrd,Eq) is a compile error.CopyrequiresCloneand cannot be combined withDrop. #[derive]on aunionis a compile error.
Combining with user-defined derives
User-defined traits can also be derived, as in #[derive(MyTrait)]. This requires MyTrait to be defined by an #[impl_derive(MyTrait)] meta block (6.8). The built-in derive names above cannot be redefined by a meta block.
Built-in and user-defined derives may be mixed freely in one #[derive(...)].
#[derive(PartialEq, Eq, Clone, MyCustomTrait)]
struct Foo { ... }
6.2.2. #[repr(...)] explicit memory representation
#[repr(C)]
struct CCompatible { x: i32; y: i32; }
#[repr(transparent)]
struct Wrapped(InnerType);
#[repr(packed)]
struct Tight { a: u8; b: u32; }
#[repr(align(16))]
struct Aligned { ... }
#[repr(u32)]
enum Status { ... }
| Argument | Meaning |
|---|---|
C | C ABI layout: declaration order, natural alignment. Same as the default struct layout |
transparent | Exactly one non-zero-size field. Same representation as that field, and crosses the C ABI as it |
packed | Packs tightly, ignoring alignment (FFI only). Cannot hold GC references; exclusive with align(N) |
align(N) | Forces a minimum alignment of N bytes |
u8 to u64 / i8 to i64 | The tag type of an enum. Widths other than 32 bits only for field-less enums; enums with data accept i32 / u32 |
Without #[repr], a struct uses declaration order and natural alignment (the same as repr(C)); the layout of enum and sums is the compiler's (2.7).
6.2.3. #[link(...)] naming an external library
#[link("m")]
extern "C" {
fn sin(x: f64) -> f64;
fn cos(x: f64) -> f64;
}
#[link(name = "openssl", kind = "static")]
extern "C" { ... }
#[link(...)] names the external library an extern block links against; it is attached to extern blocks.
| Argument form | Meaning |
|---|---|
"libname" | Plain library name |
name = "..." | Library name (key-value) |
kind = "static" / "dylib" | Link kind |
wasm_import_module = "m" | wasm32: the block's functions are imported from core module m; ignored elsewhere |
#[link_name = "sym"]
#[link_name = "sym"] on an extern function gives the symbol it links to, or its import name inside a wasm_import_module block.
#[link(wasm_import_module = "env")]
extern "C" {
#[link_name = "log_line"]
fn host_log(ptr: ConstPtr<u8>, len: usize);
}
6.2.4. #[inline] inlining hints
#[inline]
fn small_helper(x: i32) -> i32 { x + 1 }
#[inline(always)]
fn very_hot() { ... }
#[inline(never)]
fn never_inline() { ... }
| Argument | Meaning |
|---|---|
| (none) | Inlining recommended (compiler decides) |
always | Inlining forced |
never | Inlining forbidden |
#[inline] is only an optimization hint; it does not affect the program's meaning.
6.2.5. #[test] test function marker
#[test]
fn test_addition() {
assert_eq(1i32 + 1i32, 2i32);
}
assert_eq is in stdlib 2.14.5. #[test] on a function marks it as a test function. rokc test collects such functions into a harness and the test runner (rok test, etc.) runs them automatically. In ordinary builds they are excluded, as if #[cfg(test)] were also attached. A test function is run whatever its own visibility and that of its enclosing modules; #[test] does not change visibility for other code.
#[bench] marks a benchmark function and, like #[test], implies #[cfg(test)]. rokc bench collects and runs them (9.1.2). A benchmark function is run whatever its own visibility and that of its enclosing modules; #[bench] does not change visibility for other code.
6.2.6. #[cold] rarely called functions
#[cold]
fn handle_error() { ... }
#[cold] tells the compiler that the function is rarely called. The code generator then places the function outside the hot code path to improve cache friendliness.
6.2.7. #[deprecated] discouraged-use marker
#[deprecated]
fn old_api() { ... }
#[deprecated = "use new_api instead"]
fn old_api2() { ... }
Using an item marked #[deprecated] produces a warning, not an error. A message given with = "..." is included in the warning.
6.2.8. #[doc = "..."] doc comment form
#[doc = "Returns the sum of two integers."]
fn add(x: i32, y: i32) -> i32 { x + y }
A /// doc comment from lexical structure is this attribute.
/// This function ...
fn f() { ... }
// same meaning
#[doc = "This function ..."]
fn f() { ... }
The inner form #![doc] likewise has the same effect as //!.
6.2.9. #[allow(...)] / #[deny(...)] / #[warn(...)] lint control
#[allow(unused)]
fn _placeholder() { }
#[deny(deprecated)]
fn strict_zone() { ... }
These attributes change the level of lint diagnostics within the scope of the item they are attached to. Lints run in every command that compiles the package: rokc build, run, test, bench, emit, check, lint (9.1.2) and the language server (not doc). They check the package being built, not its dependencies. A warn lint is printed and does not fail a build; a deny lint is an error, and the build fails with exit code 1. allow ignores the diagnostic, warn emits it as a warning, and deny emits it as an error. Inner forms such as #![allow(..)] apply to the enclosing module (the whole file at the file head). The argument is a lint name or a group name. An item's #[allow] also covers its variants, fields and methods.
The level is determined in the following order, later overriding earlier: each lint's default level < the lints field of rok.rokon (after its lints_from chain) (9.10.3) < CLI -A / -W / -D < attributes in source (the innermost wins). lints is treated as if prepended to the CLI flags, so CLI flags written after it override it. The code of lint diagnostics is E503.
The default level of every lint is warn.
| lint | Checks |
|---|---|
unused_variables | Local variables or parameters that are never read |
unused_imports | A use whose name the module never uses |
dead_code | Non-pub functions nobody calls |
unreachable_code | Statements after return / break / continue / throw / panic |
unused_must_use | Discarding the result of a #[must_use] function, or a value of a #[must_use] type (6.2.11) |
unused_doc_comments | A doc comment on what is not documented (a statement, match arm, struct literal or tuple field, expression); use // for a plain comment |
write_to_copy | A field assigned on a copy that nothing reads afterwards: a by-value parameter or self, a for binding, or a let of an element (xs[i], 2.3.8), when no later read, loop iteration, return or call can see the change. A read that only feeds another write to the same variable does not count; a variable a closure or coroutine block mentions is not checked |
ignored_error | A try? f(); statement silently discarding an error; let _ = try? f(); marks a deliberate best-effort call and is not reported |
shadowed_loop_var | A let in a for body rebinding the loop variable |
nan_comparison | Comparison with NaN (always false; != always true) |
none_comparison | x == none instead of x is none |
bool_comparison | Comparing a bool with true / false |
self_assignment | x = x; |
unclosed_resource | A File / Dir / ReadDir / TcpStream / TcpListener / UdpSocket / Child not released by using (warn) |
while_true | while true instead of loop |
deprecated | Use of a #[deprecated] function |
redundant_cast | x as T where x is already T |
redundant_clone | .clone() on a value of a primitive type |
non_snake_case | Function, local variable, parameter, or module names not in snake_case |
non_camel_case_types | Type, trait, or variant names not in UpperCamelCase: no lowercase first letter, no __, and _ only between characters without case (V1_0) |
non_upper_case_globals | const / static names not in UPPER_SNAKE_CASE |
confusable_idents | Identifiers that resemble each other (1.4) |
missing_f_prefix | A plain string literal containing {name} where name is a local in scope: the f prefix is missing (warn) |
| Group | Includes |
|---|---|
unused | unused_variables, unused_imports, dead_code, unreachable_code, unused_must_use, unused_doc_comments |
nonstandard_style | non_snake_case, non_camel_case_types, non_upper_case_globals |
all | Every lint |
The help of write_to_copy suggests ref self / ref p, an index loop (for i in 0..xs.len() { xs[i].f = .. }), or writing the element back.
rokc lint --list prints the lints, groups, and default levels.
6.2.10. #[non_exhaustive] opting out of external exhaustiveness
#[non_exhaustive]
pub enum ApiError {
NotFound,
Unauthorized,
}
#[non_exhaustive] may be attached to an enum or struct. When present, a match on the type from an external package requires an additional catch-all (_ => ...) even if it handles every variant. This lets library authors add new variants later without breaking SemVer compatibility.
6.2.11. #[must_use] warning on ignored results
#[must_use]
fn calculate() -> i32 { ... }
Discarding the return value of a #[must_use] function unused produces a warning. To ignore the result intentionally, write _ = ... as described in parameters.
#[must_use] may also be attached to a struct or enum. Then an expression statement whose value has that type, or a local of that type that is never read, produces the unused_must_use warning with the attribute's message. The value of an async fn call or async block (a future never awaited never runs) and thread::JoinHandle (stdlib 3.4.2; a thread never joined may not finish before the program ends; detach says it runs on its own) are #[must_use].
6.2.12. #[track_caller] passing the call site
#[track_caller]
fn expect_positive(n: i32) -> i32 {
if n <= 0 { panic("not positive") } // location: where expect_positive was called
n
}
- The function receives the call site as a hidden trailing
strargument. Panics inside it report that location (4.14.13). - When a tracked function calls another tracked function, it passes on the location it received.
- A function value of a tracked function reports the location where the value was created.
- On an
async fn, only the call is tracked, not the coroutine body. - On an
extern(runtime) declaration, the symbol takes an extra location argument (8.5.7). - It cannot be attached to
main,extern "C" fn, or#[export_name]functions.
Standard library functions that panic on invalid input (panic, unreachable, assert*, indexing, slicing, unwrap, List::insert / remove, etc.) are #[track_caller].
6.2.13. Summary
The attributes above are built-in attributes recognized directly by the compiler and tools; they need no user definition through meta blocks.
Magic attributes such as #[lang] and #[literal] are bound to the meaning of the language itself and are defined in separate documents.