Motivation
The tool-handler APIs are built on the pre-AsyncFn pattern of taking a closure that returns a manually-boxed future:
// crates/baml-rt-tools/src/tools.rs (≈2011, 2910, 2922, 2952)
F: Fn(I) -> Pin<Box<dyn Future<Output = Result<O>> + Send>> + Send + Sync + 'static
Every call site pays for this with a Box::pin(async move { … }) wrapper and a per-call heap allocation. There are ~45+ such wrappers across the codebase, e.g. the 7 near-identical handlers in crates/tools/memory/src/handlers.rs:42-132, ~9 more in tools.rs itself, and others in crates/tools/system/* and crates/tools/slack/src/producer.rs:717.
AsyncFn/AsyncFnMut/AsyncFnOnce (stable since Rust 1.85) exist precisely to replace this shape.
Proposal
- Change the handler trait bounds from
Fn(I) -> Pin<Box<dyn Future<…>>> to AsyncFn(I) -> Result<O>.
- Drop the
Box::pin(async move { … }) wrappers at call sites; closures can return async blocks (or use async ||) directly.
Caveat / scope
Threading Send through an AsyncFn bound is the known rough edge — likely needs a Send-bound helper or where F::CallRefFuture<'_>: Send. Prototype on the memory handler family first and confirm the ergonomics before fanning out to tools.rs and the other tool crates.
Acceptance
- Handler registration APIs accept
AsyncFn-style closures.
Box::pin(async move …) wrappers removed at the migrated call sites.
cargo clippy --all-targets --all-features -- -D warnings clean.
Motivation
The tool-handler APIs are built on the pre-
AsyncFnpattern of taking a closure that returns a manually-boxed future:Every call site pays for this with a
Box::pin(async move { … })wrapper and a per-call heap allocation. There are ~45+ such wrappers across the codebase, e.g. the 7 near-identical handlers incrates/tools/memory/src/handlers.rs:42-132, ~9 more intools.rsitself, and others incrates/tools/system/*andcrates/tools/slack/src/producer.rs:717.AsyncFn/AsyncFnMut/AsyncFnOnce(stable since Rust 1.85) exist precisely to replace this shape.Proposal
Fn(I) -> Pin<Box<dyn Future<…>>>toAsyncFn(I) -> Result<O>.Box::pin(async move { … })wrappers at call sites; closures can return async blocks (or useasync ||) directly.Caveat / scope
Threading
Sendthrough anAsyncFnbound is the known rough edge — likely needs aSend-bound helper orwhere F::CallRefFuture<'_>: Send. Prototype on the memory handler family first and confirm the ergonomics before fanning out totools.rsand the other tool crates.Acceptance
AsyncFn-style closures.Box::pin(async move …)wrappers removed at the migrated call sites.cargo clippy --all-targets --all-features -- -D warningsclean.