Attributes
#[name(value)] attaches metadata to a field, function, or object. Unlike a comment or a compiler-only annotation, an attribute is real data — every one you write is queryable at runtime as a name -> value map, and the value can be anything: a string, a number, a map, even a function.
Attributes You've Already Used
A few show up before you'd think to call them "attributes" at all:
#[main]— marks a function to run when the document runs#[readonly]/#[private]— the field access modifiers from Fields#[type]— marks an object as a formal prototype, covered later in Prototypes & Schemas#[async]— marks a function as async, covered later in Async
Each of those is really just Stof's parser watching for a specific attribute name and reacting to it — there's nothing more privileged about #[main] than an attribute you make up yourself.
Querying Attributes at Runtime
this.attributes() returns the current function's own attributes; self.attributes('name') returns a named field's:
#[meta({"meaning": "everything"})]
field: 42
#[main]
#[purpose("testing general attributes")]
fn main() {
const my_attrs = this.attributes();
pln(my_attrs.get("purpose"));
const field_attrs = self.attributes("field");
const meta = field_attrs.get("meta");
pln(meta.get("meaning"));
}Both #[main] and #[purpose(...)] on main() show up in this.attributes() — the attribute you're using to mark something runnable is visible right alongside any attribute you made up yourself.
Custom Attributes as Metadata
Because an attribute's value can be anything, this becomes a real tool: attach a validation rule, a range, a label, whatever a field needs — as data, next to the field, instead of in a separate schema file that has to stay in sync:
#[range((0, 100))]
percent: 42
fn checkRange(field: str) -> bool {
const attrs = self.attributes(field);
const range = attrs.get("range");
const val = self.get(field);
val >= range[0] && val <= range[1]
}
#[main]
fn main() {
pln(self.checkRange("percent"));
}This is a small, hand-rolled version of a real pattern — #[schema(...)] is the built-in attribute for exactly this, and Prototypes & Schemas covers it properly. Objects take attributes the same way fields and functions do, so this isn't limited to scalar values.