Typed Hooks
The Hook trait passes dataset values as &dyn Any, requiring manual downcasting. TypedHook<T> provides type-safe access — you implement methods that receive &T directly, and non-matching types are silently ignored.
The TypedHook<T> trait
pub trait TypedHook<T: 'static>: Sync {
fn after_load(&self, _n: &dyn StepInfo, _ds: &DatasetRef, _value: &T) -> Result<(), HookAbort> {
Ok(())
}
fn before_save(&self, _n: &dyn StepInfo, _ds: &DatasetRef, _value: &T) -> Result<HookControl, HookAbort> {
Ok(HookControl::Continue)
}
}
after_loadfires after a dataset of typeTis loaded (corresponds toHook::after_dataset_loaded)before_savefires before a dataset of typeTis saved (corresponds toHook::before_dataset_saved)
Datasets of other types are silently ignored — the adapter checks the type at runtime and skips non-matching values.
Using typed hooks
A TypedHook<T> cannot be used directly in a hooks tuple because the tuple expects Hook impls. Call .typed() to wrap it in a TypedHookAdapter that implements Hook:
let hooks = (MyTypedHook.typed(),);
The .typed() method is provided by IntoTypedHook<T>, which has a blanket implementation for all TypedHook<T> + Sized + Sync types.
Example: recording loaded values
use std::sync::{Arc, Mutex};
struct I32Recorder(Arc<Mutex<Vec<i32>>>);
impl TypedHook<i32> for I32Recorder {
fn after_load(&self, _n: &dyn StepInfo, _ds: &DatasetRef, value: &i32) -> Result<(), HookAbort> {
self.0.lock().unwrap().push(*value);
Ok(())
}
}
Register it with .typed():
let recorded = Arc::new(Mutex::new(Vec::new()));
App::new(catalog, params)
.with_hooks((I32Recorder(Arc::clone(&recorded)).typed(),))
.execute(pipeline)?;
// recorded now contains all i32 values that were loaded during the pipeline run
// String, f64, and other dataset types were silently ignored
Example: validation before save
struct RejectNegative;
impl TypedHook<i32> for RejectNegative {
fn before_save(&self, _n: &dyn StepInfo, _ds: &DatasetRef, value: &i32) -> Result<HookControl, HookAbort> {
if *value < 0 {
Err(HookAbort("negative value rejected"))
} else {
Ok(HookControl::Continue)
}
}
}
If any node tries to save a negative i32, the pipeline stops with PondError::HookAbort("negative value rejected"). Saves of other types (e.g. String) pass through unaffected.
Combining with other hooks
Typed hooks compose with regular hooks in the same tuple:
.with_hooks((
LoggingHook::new(),
RejectNegative.typed(),
))
You can also have multiple typed hooks for different types:
.with_hooks((
I32Recorder(state).typed(),
StringValidator.typed(),
))