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

Execution modes and contexts

xlfn separates Excel-visible arguments from injected capabilities. A context, when present, must be the first parameter and must be passed by value with exactly one #[excel_context(...)] role.

Main-thread context

#![allow(unused)]
fn main() {
#[excel_function(name = "APP.ENVIRONMENT")]
fn environment(
    #[excel_context(main_thread)] context: MainThreadContext<'_, AppTools>,
) -> String {
    context.state().environment.clone()
}
}

MainThreadContext is neither Send nor Sync. It has one inferred lifetime tied to the current Excel-call scope; the context keeps the open generation alive while exposing state and callback capability. With the rtd feature, context.rtd() returns the narrower RTD capability that establishes a streaming subscription. Formula-owned object producers use main-thread return semantics, even when they do not explicitly request a context.

Do not combine a main-thread context with thread_safe.

Thread-safe context

#![allow(unused)]
fn main() {
#[excel_function(name = "APP.VERSION", thread_safe)]
fn version(
    #[excel_context(thread_safe)] context: ThreadSafeContext<'_, AppTools>,
) -> String {
    context.state().version.clone()
}
}

ThreadSafeContext is Copy, Send, and Sync when the referenced state permits it. Its presence marks the function as thread-safe even if the function attribute omits the flag. Use an explicit attribute as well when it improves readability, but do not treat duplicate declaration as additional safety.

What thread_safe guarantees

thread_safe declares that Excel may invoke the generated boundary concurrently on calculation threads. It does not make State or anything reached through State thread-safe. Every application resource used by the function must independently support concurrent access or be protected by an application synchronization or dispatch policy.

Do not move call-scoped Excel values, raw references, or callback capabilities to another thread. Thread affinity below the Rust worksheet-function boundary is a separate application concern from Excel’s execution mode.

Macro-sheet context

#![allow(unused)]
fn main() {
#[excel_function(name = "APP.RANGE.NAME")]
fn range_name(
    #[excel_context(macro_sheet)] context: MacroSheetContext<'_, AppTools>,
    #[excel_arg(reference)] reference: ExcelReference<'_>,
) -> XllResult<String> {
    context.sheet_name(&reference)
}
}

A macro-sheet context permits Excel callback operations that are not allowed in thread-safe functions. It is neither Send nor Sync; its one inferred lifetime is the current Excel-call scope. It provides:

  • coerce for an owned ExcelValue;
  • coerce_matrix<T> for an owned matrix;
  • sheet_name.

The macro_sheet function flag selects the same registration capability without injecting state access. It is incompatible with thread_safe and asynchronous functions.

Asynchronous context

#![allow(unused)]
fn main() {
#[excel_function(name = "APP.SLOW")]
async fn slow(
    #[excel_context(asynchronous)] context: AsyncContext<'_, AppTools>,
    input: String,
) -> XllResult<String> {
    context.check_cancelled()?;
    Ok(input)
}
}

The framework-owned future retains the current open-generation lease and per-call cancellation token. AsyncContext<'_, AppTools> borrows those capabilities for the invocation, so it is available only with the async feature and only to async fn; it cannot escape into a detached task. An async function may omit the context if it does not need state or cancellation.

Compatibility table

ModeHow selectedExcel MTRCan use raw referencesCan return a new handle object
Main threaddefault or main_thread contextnonoyes
Thread-safethread_safe or thread_safe contextyesnono
Macro-sheetmacro_sheet or macro_sheet contextnoyesno
Asynchronousasync fnnative async ABInono

A function marked volatile must still return a type valid for its mode. Handle objects and HandleAlias<'_, T> support volatile main-thread return semantics. Borrowed Handle<'_, T> values are synchronous call-scoped inputs and cannot be used in async functions. An async function that needs a formula-owned object must use the generation-scoped HandleLease<'_, T> input, which pins the registry payload before the task is committed.