telepath-macros 0.2.1

Proc-macro crate for Telepath (#[command] attribute)
Documentation

telepath-macros

Proc-macro crate providing the #[command] attribute that registers a plain Rust function as a Telepath RPC command — no hand-written dispatch boilerplate required.

This crate is a build-time only dependency of telepath-server. It does not appear in device firmware at runtime.

Usage

use telepath_server::command;

// Plain command — all args are wire args
#[command]
fn ping() -> u32 {
    0xDEAD_BEEF
}

#[command]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Resource injection — peripheral state passed from ResourceRegistry, not over the wire
#[command]
fn set_led(#[resource] led: &mut MyLed, on: bool) -> bool {
    led.set(on)
}

The #[command] attribute is re-exported from telepath-server; no direct dependency on telepath-macros is required in downstream crates.

Generated items

For each #[command] fn foo(…) -> R the macro emits:

Item Description
fn __telepath_shim_foo(input: &[u8], output: &mut [u8]) -> Result<usize, DispatchError> Type-erased shim: deserializes args via postcard, calls foo, serializes return value
fn __telepath_args_schema_foo(out: &mut [u8]) -> Result<usize, ()> Writes postcard-encoded NamedType schema for the argument tuple
fn __telepath_ret_schema_foo(out: &mut [u8]) -> Result<usize, ()> Writes postcard-encoded NamedType schema for the return type
pub const __TELEPATH_CMD_FOO: CommandMetadata Const holding name, cmd_id, and function pointers
static __TELEPATH_REG_FOO: CommandMetadata #[linkme::distributed_slice] registration — zero-cost link-time registration in TELEPATH_COMMANDS

The original function body is preserved unchanged and remains directly callable.

Signature contract

Allowed:

  • Free functions only (no self receiver)
  • Any number of positional arguments with simple identifier patterns
  • Wire argument types: any T: Serialize + DeserializeOwned + postcard_schema::Schema (owned, no references)
  • #[resource]-annotated arguments: &T or &mut T where T: 'static — injected from the server's ResourceRegistry, not deserialized from the wire
  • Wire and resource arguments may appear in any order
  • Return type: any T: Serialize + postcard_schema::Schema (owned, no references); () means "no payload"

Rejected at compile time:

  • async fn, unsafe fn
  • Generic parameters or where clauses
  • &T / &mut T argument without the #[resource] attribute
  • &T / &mut T return type
  • Methods (fn foo(&self, …))
  • Non-identifier argument patterns (e.g. tuple destructuring (a, b): (i32, i32))
  • Duplicate #[resource] types (each resource type may appear at most once)

Wire encoding

  • Args: only non-#[resource] arguments are serialized; resource arguments are server-side only. Serialized as a postcard tuple — () (zero wire args), (T,) (one wire arg), (T1, T2, …) (N wire args).
  • Return value: serialized standalone (no wrapper tuple)
  • cmd_id: derived deterministically from (name, args_type_str, ret_type_str) using wire args only via FNV-1a 16-bit. Adding or removing a #[resource] argument does not change the wire cmd_id. Renaming a function or changing a wire argument type is a breaking wire change.
  • Reserved cmd_id = 0x0000 is avoided by deterministic salt rehashing in derive_cmd_id; the discovery ID is never emitted by user commands

Build

# Built automatically as part of the workspace
cargo build --workspace

# Inspect macro expansion in a consumer crate (requires cargo-expand)
cd telepath-server && cargo expand --test macro_smoke

telepath-macros is a workspace member targeting the native host (proc-macro crates run on the build host, not the embedded target). No cross-compilation is needed.

Toolchain

Stable Rust (pinned in rust-toolchain.toml at the repo root). Changes MUST NOT break existing callers on the pinned stable channel.