# registry-io — API Reference
This document describes every public item in `registry-io 0.4.0`, with
parameter details, return values, and multiple code examples per use case.
For internal architecture notes, see [ARCHITECTURE.md](./ARCHITECTURE.md) (when
present). For performance characteristics, see [PERFORMANCE.md](./PERFORMANCE.md).
---
## Table of contents
- [Module: `registry_io`](#module-registry_io)
- [`VERSION`](#version)
- [Type: `HandlerId`](#type-handlerid)
- [`HandlerId::as_u64`](#handleridas_u64)
- [Type: `PanicInfo<'a>`](#type-panicinfoa)
- [`PanicInfo::handler_id`](#panicinfohandler_id)
- [`PanicInfo::payload`](#panicinfopayload)
- [`PanicInfo::message`](#panicinfomessage)
- [Type: `SyncRegistry<E>`](#type-syncregistrye)
- [`SyncRegistry::new`](#syncregistrynew)
- [`SyncRegistry::with_capacity`](#syncregistrywith_capacity)
- [`SyncRegistry::register`](#syncregistryregister)
- [`SyncRegistry::register_with_priority`](#syncregistryregister_with_priority)
- [`SyncRegistry::register_guard`](#syncregistryregister_guard)
- [`SyncRegistry::register_guard_with_priority`](#syncregistryregister_guard_with_priority)
- [`SyncRegistry::unregister`](#syncregistryunregister)
- [`SyncRegistry::clear`](#syncregistryclear)
- [`SyncRegistry::contains`](#syncregistrycontains)
- [`SyncRegistry::handler_count`](#syncregistryhandler_count)
- [`SyncRegistry::is_empty`](#syncregistryis_empty)
- [`SyncRegistry::on_panic`](#syncregistryon_panic)
- [`SyncRegistry::clear_panic_callback`](#syncregistryclear_panic_callback)
- [`SyncRegistry::notify`](#syncregistrynotify)
- [Type: `HandlerGuard<E>`](#type-handlerguarde)
- [`HandlerGuard::id`](#handlerguardid)
- [`HandlerGuard::forget`](#handlerguardforget)
- [`HandlerGuard` drop semantics](#handlerguard-drop-semantics)
- [Trait implementations](#trait-implementations)
---
## Module: `registry_io`
The crate root re-exports every public item. Import directly:
```rust
use registry_io::{HandlerGuard, HandlerId, PanicInfo, SyncRegistry, VERSION};
```
### `VERSION`
```rust
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
```
Crate version string populated at build time. Useful for diagnostics.
```rust
println!("running registry-io {}", registry_io::VERSION);
```
---
## Type: `HandlerId`
```rust
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct HandlerId(/* opaque */);
```
Opaque identifier returned by every `register*` call. Used to refer back to a
registration later (e.g. for [`SyncRegistry::unregister`]).
`HandlerId` is `Copy + Eq + Hash + Debug + Display`. The numeric representation
is intentionally hidden and may change between releases. **Do not** persist or
compare ids across registries — they are only valid for the registry that
issued them.
### `HandlerId::as_u64`
```rust
pub const fn as_u64(self) -> u64;
```
Returns the raw numeric value backing this id, for diagnostic use only.
**Returns:** the underlying `u64`.
**Example — log the id of a freshly registered handler:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
## Type: `PanicInfo<'a>`
```rust
pub struct PanicInfo<'a> { /* opaque */ }
```
Snapshot passed to an `on_panic` callback when a handler invocation panics
inside [`SyncRegistry::notify`]. Borrowed because the panic payload is owned
by the registry only for the duration of the callback.
### `PanicInfo::handler_id`
```rust
pub fn handler_id(&self) -> HandlerId;
```
The [`HandlerId`] of the handler that panicked.
**Example — react to a specific failing handler:**
```rust
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
let failing_id = Arc::new(AtomicU64::new(0));
let sink = Arc::clone(&failing_id);
registry.on_panic(move |info| {
sink.store(info.handler_id().as_u64(), Ordering::SeqCst);
});
type.
**Example — downcast a custom panic type:**
```rust
use std::sync::{Arc, Mutex};
use registry_io::SyncRegistry;
#[derive(Debug, PartialEq, Eq)]
struct MyErr(i32);
let registry: SyncRegistry<()> = SyncRegistry::new();
let captured: Arc<Mutex<Option<i32>>> = Arc::new(Mutex::new(None));
let sink = Arc::clone(&captured);
registry.on_panic(move |info| {
if let Some(err) = info.payload().downcast_ref::<MyErr>() {
*sink.lock().unwrap() = Some(err.0);
}
});
was a `&'static str` (from `panic!("literal")`) or a `String` (from
`panic!("{}", value)`), `None` for custom panic types.
**Example — log every panic message:**
```rust
use std::sync::{Arc, Mutex};
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
let log: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let sink = Arc::clone(&log);
registry.on_panic(move |info| {
sink.lock().unwrap().push(info.message().unwrap_or("<opaque>").into());
});
assert_eq!(log.lock().unwrap().as_slice(), &["alpha".to_owned(), "beta 2".to_owned()]);
```
---
## Type: `SyncRegistry<E>`
```rust
pub struct SyncRegistry<E: Send + Sync + 'static> { /* opaque */ }
```
The core synchronous registry. Stores handlers as `Arc<dyn Fn(&E) + Send + Sync + 'static>`
and dispatches via a lock-free [`arc_swap::ArcSwap`] snapshot.
`E` is the event type. Handlers receive `&E` (a borrow) so events don't need to
be `Clone`. The bound `E: Send + Sync + 'static` is required for the registry
to itself be `Send + Sync`.
### `SyncRegistry::new`
```rust
pub fn new() -> Self;
```
Construct an empty registry.
**Returns:** a fresh `SyncRegistry<E>` with no registered handlers.
**Example — minimal:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<u32> = SyncRegistry::new();
assert!(registry.is_empty());
```
**Example — using turbofish:**
```rust
use registry_io::SyncRegistry;
let registry = SyncRegistry::<&'static str>::new();
let _ = registry.register(|s| println!("received {s}"));
registry.notify(&"hello");
```
### `SyncRegistry::with_capacity`
```rust
pub fn with_capacity(capacity: usize) -> Self;
```
Construct an empty registry whose internal `Vec` is pre-allocated to hold
`capacity` handlers. Slow-path optimization for registries with a known
steady-state size.
**Parameters:**
- `capacity: usize` — number of handler slots to pre-allocate.
**Example — pre-allocate when steady-state size is known:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<u64> = SyncRegistry::with_capacity(64);
for _ in 0..32 {
let _ = registry.register(|_| {});
}
```
### `SyncRegistry::register`
```rust
pub fn register<F>(&self, handler: F) -> HandlerId
where
F: Fn(&E) + Send + Sync + 'static;
```
Register a handler at default priority (`0`). Returns a [`HandlerId`] usable
for later unregistration.
**Parameters:**
- `handler: F` — a closure that receives `&E` on every `notify` call. Must be
`Fn + Send + Sync + 'static`.
**Returns:** an opaque `HandlerId` unique within this registry.
**Example — simple side-effect handler:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<&str> = SyncRegistry::new();
let _id = registry.register(|s| println!("event: {s}"));
registry.notify(&"first");
```
**Example — accumulator with shared state:**
```rust
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use registry_io::SyncRegistry;
let registry: SyncRegistry<u64> = SyncRegistry::new();
let total = Arc::new(AtomicU64::new(0));
let sink = Arc::clone(&total);
let _ = registry.register(move |v| {
let _ = sink.fetch_add(*v, Ordering::Relaxed);
});
registry.notify(&5);
registry.notify(&7);
assert_eq!(total.load(Ordering::Relaxed), 12);
```
### `SyncRegistry::register_with_priority`
```rust
pub fn register_with_priority<F>(&self, priority: i32, handler: F) -> HandlerId
where
F: Fn(&E) + Send + Sync + 'static;
```
Register a handler with an explicit priority. On `notify`, handlers fire in
**descending priority order**; ties are broken in registration order.
**Parameters:**
- `priority: i32` — higher values fire first. `0` is the default used by
`register`.
- `handler: F` — the handler closure (same bounds as `register`).
**Returns:** an opaque `HandlerId`.
**Example — high-priority logging hook fires before defaults:**
```rust
use std::sync::{Arc, Mutex};
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
let trace = Arc::new(Mutex::new(Vec::<&'static str>::new()));
let t = Arc::clone(&trace);
registry.notify(&());
assert_eq!(trace.lock().unwrap().as_slice(), &["audit", "business"]);
```
**Example — negative priority for cleanup handlers:**
```rust
use std::sync::{Arc, Mutex};
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
let trace = Arc::new(Mutex::new(Vec::<&'static str>::new()));
let t = Arc::clone(&trace);
registry.notify(&());
assert_eq!(trace.lock().unwrap().as_slice(), &["work", "cleanup"]);
```
### `SyncRegistry::register_guard`
```rust
pub fn register_guard<F>(self: &Arc<Self>, handler: F) -> HandlerGuard<E>
where
F: Fn(&E) + Send + Sync + 'static;
```
Register and return a RAII [`HandlerGuard`] that automatically unregisters
when dropped. Requires the registry to be wrapped in [`Arc`] so the guard
can hold a [`Weak`] reference.
**Parameters:**
- `handler: F` — handler closure.
**Returns:** a `HandlerGuard<E>`. While the guard is alive the handler stays
registered; dropping the guard removes it.
**Example — handler tied to a scope:**
```rust
use std::sync::Arc;
use registry_io::SyncRegistry;
let registry = Arc::new(SyncRegistry::<u32>::new());
{
let _guard = registry.register_guard(|n| println!("scoped: {n}"));
registry.notify(&1);
}
assert!(registry.is_empty());
```
**Example — guard returned from a builder function:**
```rust
use std::sync::Arc;
use registry_io::{HandlerGuard, SyncRegistry};
fn install_logger(registry: &Arc<SyncRegistry<String>>) -> HandlerGuard<String> {
registry.register_guard(|msg| println!("[log] {msg}"))
}
let registry = Arc::new(SyncRegistry::<String>::new());
let _log_guard = install_logger(®istry);
registry.notify(&"hello".to_owned());
```
### `SyncRegistry::register_guard_with_priority`
```rust
pub fn register_guard_with_priority<F>(
self: &Arc<Self>,
priority: i32,
handler: F,
) -> HandlerGuard<E>
where
F: Fn(&E) + Send + Sync + 'static;
```
Combines [`register_with_priority`] and [`register_guard`].
**Example — auditing hook with priority 100:**
```rust
use std::sync::Arc;
use registry_io::SyncRegistry;
let registry = Arc::new(SyncRegistry::<&'static str>::new());
let _audit_guard = registry.register_guard_with_priority(100, |evt| {
println!("AUDIT: {evt}");
});
registry.notify(&"login");
```
### `SyncRegistry::unregister`
```rust
pub fn unregister(&self, id: HandlerId) -> bool;
```
Remove the handler identified by `id`.
**Parameters:**
- `id: HandlerId` — id returned from a previous `register*` call.
**Returns:** `true` if a handler was found and removed, `false` otherwise.
**Example — remove a single handler:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
```rust
pub fn clear(&self);
```
Remove every registered handler. In-flight `notify` calls that loaded a
snapshot before `clear` finishes will still iterate over their snapshot to
completion.
**Example — clear after a test:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
for _ in 0..10 {
let _ = registry.register(|_| {});
}
registry.clear();
assert_eq!(registry.handler_count(), 0);
```
### `SyncRegistry::contains`
```rust
pub fn contains(&self, id: HandlerId) -> bool;
```
Returns `true` if a handler with `id` is currently registered.
**Example:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
**Example:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
assert_eq!(registry.handler_count(), 0);
```
### `SyncRegistry::is_empty`
```rust
pub fn is_empty(&self) -> bool;
```
Equivalent to `self.handler_count() == 0`.
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
assert!(registry.is_empty());
callback (if any) is replaced. Without an installed callback, panics are
**silently absorbed** — siblings still fire, but the panic is not observable.
A panic inside the callback itself is caught and discarded.
**Parameters:**
- `callback: F` — closure invoked once per panicking handler, on the thread
that invoked `notify`.
**Example — count panics:**
```rust
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
let count = Arc::new(AtomicUsize::new(0));
let sink = Arc::clone(&count);
let _ = registry.register(|_| {});
registry.notify(&());
assert_eq!(count.load(Ordering::Relaxed), 1);
```
**Example — log via your existing logger:**
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
registry.on_panic(|info| {
eprintln!(
"handler {} panicked: {}",
info.handler_id(),
info.message().unwrap_or("<opaque>")
);
});
```
### `SyncRegistry::clear_panic_callback`
```rust
pub fn clear_panic_callback(&self);
```
Remove any previously installed `on_panic` callback. Subsequent handler
panics during `notify` become silent again.
```rust
use registry_io::SyncRegistry;
let registry: SyncRegistry<()> = SyncRegistry::new();
and **allocation-free** in the no-panic case. Handlers run inline on the
calling thread, in **priority order** (high → low), ties broken in
registration order.
Each handler invocation is wrapped in [`catch_unwind`] so that a panic in
one handler does **not** propagate to siblings or to the caller. If an
[`on_panic`](#syncregistryon_panic) callback is installed, it is invoked once
per panicking handler.
**Parameters:**
- `event: &E` — borrowed event. Handlers receive the same reference.
**Returns:** unit. Errors and panics are absorbed internally.
**Example — broadcasting a typed event:**
```rust
use registry_io::SyncRegistry;
#[derive(Debug)]
struct ConfigReloaded { keys_changed: usize }
let registry: SyncRegistry<ConfigReloaded> = SyncRegistry::new();
let _ = registry.register(|evt| {
println!("config changed: {} keys", evt.keys_changed);
});
registry.notify(&ConfigReloaded { keys_changed: 4 });
```
**Example — fan-out from a hot loop:**
```rust
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use registry_io::SyncRegistry;
let registry: SyncRegistry<u32> = SyncRegistry::new();
let counter = Arc::new(AtomicU64::new(0));
let sink = Arc::clone(&counter);
let _ = registry.register(move |v| {
let _ = sink.fetch_add(u64::from(*v), Ordering::Relaxed);
});
for i in 0..1_000 {
registry.notify(&i);
}
assert_eq!(counter.load(Ordering::Relaxed), (0..1_000).sum::<u32>() as u64);
```
---
## Type: `HandlerGuard<E>`
```rust
#[must_use = "..."]
pub struct HandlerGuard<E: Send + Sync + 'static> { /* opaque */ }
```
RAII handle for a registered handler. Drop the guard to unregister. The
guard holds a [`Weak`] reference to the registry, so it doesn't keep the
registry alive on its own.
### `HandlerGuard::id`
```rust
pub fn id(&self) -> HandlerId;
```
Return the underlying [`HandlerId`].
```rust
use std::sync::Arc;
use registry_io::SyncRegistry;
let registry = Arc::new(SyncRegistry::<()>::new());
alive until explicit `unregister` or registry drop.
```rust
use std::sync::Arc;
use registry_io::SyncRegistry;
let registry = Arc::new(SyncRegistry::<()>::new());