Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Attribute reference

This chapter is the compact reference for xlfn’s procedural macros. The compiler validates incompatible combinations; prefer the smallest attribute set that accurately describes the Excel contract.

Generated code resolves the framework’s dependency name from Cargo.toml, including renamed dependencies. The crate = "path" option can override that resolution for each macro.

Default names derived from Rust identifiers omit a raw identifier’s r# prefix: fn r#type(r#match: f64) registers as type with an argument named match. The same rule applies to add-in and enum names. Explicit string options keep their specified spelling.

#[excel_addin(...)]

Place exactly one #[excel_addin] on a non-generic struct declared at the crate root:

#![allow(unused)]
fn main() {
#[excel_addin(
    name = "App Tools",
    id = "app-tools",
    category = "AppTools"
)]
pub struct AppTools;
}
OptionMeaningDefault
name = "..."Display name shown by Excel’s Add-in ManagerRust struct name
id = "..."Stable add-in identity used by runtime ownership and registrationlowercase Rust struct name
category = "..."Default Function Wizard categorydisplay name
physical_unloadOpt into physical DLL unload after an unsafe quiescence contractdisabled

name and category must contain 1 through 255 UTF-16 code units. id must be a non-reserved ASCII slug of at most 64 bytes, begin with a letter, and contain only letters, digits, -, or _.

The macro emits the standard XLL lifecycle and COM exports. Implement Addin for the attributed type.

The default keeps the module resident after terminal removal because a safe Addin cannot account for executable sources created through arbitrary Rust threads or native callbacks. physical_unload is an unsafe opt-in: use it only together with unsafe impl PhysicallyUnloadableAddin for YourAddin {} and stop every such source before the stronger quiescence hook returns.

#[excel_function(...)]

Apply the attribute to an ordinary or async free function:

#![allow(unused)]
fn main() {
/// Computes the area of a rectangle.
#[excel_function(
    name = "MATH.SCALE",
    id = "math_scale_v1",
    category = "Math",
    help_topic = "https://docs.example.invalid/math/scale",
    thread_safe
)]
fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}
}
OptionMeaningDefault
name = "..."Excel-visible function nameRust function identifier
id = "..."Stable UDF identity and generated export identityRust function identifier
category = "..."Function Wizard categoryadd-in default when omitted or empty
description = "..."Function Wizard descriptionjoined Rust doc comments
help_topic = "..."Help URL or topic supplied to Excelempty
thread_safeRegister for Excel multi-threaded recalculationdisabled
macro_sheetRegister with macro-sheet capabilitydisabled
volatileRecalculate whenever Excel recalculatesdisabled
hiddenHide the function from the Function Wizardvisible

Use a stable explicit id before publishing workbooks. Changing an ID changes framework identity even when the Excel-visible name remains unchanged.

The macro supports at most 255 Excel-visible parameters for synchronous functions and 254 for async functions; Excel’s async handle consumes the remaining ABI slot. The optional injected context is not Excel-visible.

Function flag constraints

  • thread_safe is incompatible with main-thread and macro-sheet contexts.
  • macro_sheet is incompatible with thread_safe and async functions.
  • reference arguments require macro-sheet capability.
  • async functions cannot accept reference arguments.
  • a return type must implement the marker trait for the selected execution mode.
  • a volatile function’s return type must also implement VolatileReturn.

See Execution modes and contexts and Conversion reference.

#[excel_context(...)]

A function may have at most one injected context. It must be the first parameter, passed by value, and must not also carry #[excel_arg].

#![allow(unused)]
fn main() {
fn lookup(
    #[excel_context(thread_safe)] context: ThreadSafeContext<'_, AppTools>,
    key: String,
) -> XllResult<f64> {
    context.state().lookup(&key)
}
}
RoleRust contextCapability
main_threadMainThreadContext<'_, AppTools>main-thread Excel callbacks, handles, RTD
thread_safeThreadSafeContext<'_, AppTools>shared state during MTR; no unsafe Excel callbacks
macro_sheetMacroSheetContext<'_, AppTools>Excel references and macro-sheet registration
asynchronousAsyncContext<'_, AppTools>cancellation and shared state for an async UDF

An async fn may omit a context. When it has one, the role must be asynchronous. A synchronous function cannot use the asynchronous role.

#[excel_arg(...)]

Annotate an Excel-visible parameter to improve Function Wizard metadata or define presence/reference policy:

#![allow(unused)]
fn main() {
fn interpolate(
    #[excel_arg(
        name = "Method",
        description = "Interpolation method.",
        default = Method::Linear,
        missing = "default",
        blank = "error"
    )]
    method: Method,
) -> f64 {
    // ...
}
}
OptionMeaning
name = "..."Excel-visible argument name
description = "..."Function Wizard argument description
default = <expr>Rust expression used by a selected default policy
blank = "default"use default for an empty cell
blank = "error"reject an empty cell explicitly
missing = "default"use default for an omitted trailing argument
missing = "error"reject an omitted argument explicitly
referencereceive an unevaluated Excel reference

Rules:

  • default requires at least one blank = "default" or missing = "default" policy.
  • a "default" policy requires default = ....
  • reference cannot be combined with blank, missing, or default policies.
  • reference requires macro_sheet or MacroSheetContext.
  • argument patterns must be simple identifiers; destructuring belongs inside the function body.

Without an explicit presence policy, the parameter’s conversion type controls blank and missing behavior. See Optional arguments and enums.

#[derive(ExcelEnum)]

Derive strict text conversion for a fieldless enum:

#![allow(unused)]
fn main() {
}

#[derive(Clone, Copy, ExcelEnum)]
#[excel_enum(ascii_case_insensitive)]
enum Direction {
    #[excel_value(name = "Forward")]
    Forward,
    #[excel_value(name = "Reverse")]
}
  • variants must be unit variants;
  • names default to Rust variant identifiers;
  • #[excel_value(name = "...")] assigns the worksheet spelling;
  • #[excel_enum(ascii_case_insensitive)] enables ASCII case-insensitive matching;
  • effective names must be non-empty and unique under the selected comparison policy.

The derive implements input conversion, normalized variant identity for formula-revision handle inputs, scalar output conversion, and all execution-mode return markers.

#[derive(ExcelHandleObject)]

Derive this marker for an object that Excel formulas may own through an opaque typed handle:

#![allow(unused)]
fn main() {
#[derive(ExcelHandleObject)]
struct Dataset {
    // immutable or internally synchronized state
}
}

The type must satisfy Any + Send + Sync + 'static. Returning the object publishes it for the producer formula’s revision; a changed formula revision publishes a new object while a same-revision recalculation reuses the memoized object. Accepting Handle<'_, Dataset> resolves and type-checks the token. HandleAlias<'_, Dataset> is the explicit main-thread return capability for republishing an existing object. Borrowed Handle values are not return values, and cannot be used by thread-safe, macro-sheet, or async functions. Async functions that need an existing object use HandleLease<'_, Dataset>, which is a generation-scoped input created by pinning the authenticated registry object at the async boundary.

See Formula-owned handles.

Treat compile errors as contract failures

The macros deliberately reject ambiguous or unsound declarations. Do not work around a diagnostic by weakening flags or changing an argument to a dynamic type without understanding the Excel ABI consequence. Compile-fail tests are appropriate for your own macro policies and published examples.