App & Debugging in no_std
Compiling a no_std pipeline with std
One of the strengths of pondrs is that a pipeline written for no_std can also be compiled with std enabled. This lets you use the full App interface β including YAML configuration, CLI argument parsing, parameter overrides, check, and viz β for development and debugging, while deploying the same pipeline code to a no_std target.
Simply add pondrs as a dev-dependency with the all feature, and use App::from_args or App::from_yaml in a host-side binary or test:
// tests/run_on_host.rs β compiles with std
fn main() -> Result<(), PondError> {
App::from_args(std::env::args_os())?
.dispatch(my_no_std_pipeline)
}
This gives you visualization, validation, logging hooks, and parallel execution for free during development, without changing the pipeline itself.
App::new
In no_std environments, App::new is the only available constructor:
let app = App::new(catalog, params);
app.execute(pipeline)?;
There is no YAML loading, no CLI parsing, and no dispatch β you construct the catalog and params directly and call execute.
Default runner
The default runner tuple in no_std is (SequentialRunner,). The ParallelRunner requires std (threads).
Hooks in no_std
The Hook and Hooks traits work in no_std, but there are no built-in hook implementations (LoggingHook requires std). You can implement your own:
struct UartLogger;
impl Hook for UartLogger {
fn before_node_run(&self, n: &dyn StepMeta) {
// write to UART, toggle debug pin, etc.
uart_print(n.name());
}
fn on_node_error(&self, n: &dyn StepMeta, _error: &str) {
uart_print("ERROR: ");
uart_print(n.name());
}
}
App::new(catalog, params)
.with_hooks((UartLogger,))
.execute(pipeline)?;
Note that in no_std, the error string in on_node_error is always "node error" (not the full error message), since Display formatting requires allocation.
Dataset names
In no_std, the catalog indexer is not available, so DatasetRef::name in hook callbacks is always None. Hooks can still use ds.id (pointer-based) or ds.meta.type_string() for identification.
PondError in no_std
Only three PondError variants are available without std:
DatasetNotLoadedβ a dataset was read before being writtenRunnerNotFoundβ the specified runner name doesnβt match any runnerCheckFailedβ pipeline validation failed (used bydispatchwithCommand::Check)
All other variants (Io, Polars, SerdeYaml, etc.) are feature-gated.
Validation
check() works fully in no_std. It uses fixed-size stack arrays instead of HashMap:
let steps = pipeline(&catalog, ¶ms);
steps.check()?;
// For pipelines with more than 20 datasets:
steps.check_with_capacity::<64>()?;
Warnings
for_each_warning also works fully in no_std. It reports datasets whose
identity or name is unreliable rather than structural errors, and hands each
one to a callback instead of collecting them β so it needs no allocator and you
supply your own sink:
use core::fmt::Write as _;
steps.for_each_warning(&mut |w| {
let mut buf = heapless::String::<256>::new();
let _ = write!(buf, "warning: {w}");
uart_print(&buf);
});
Two warnings are reported:
ZeroSizedDatasetβ a zero-sized dataset type has no reliable address, so it may collide with a sibling catalog field.UnconventionalDatasetTypeβ the type ident does not end inDataset. This fires underno_stdtoo, even though there is no indexer here: the convention is a property of the type, and the same catalog is typically also compiled for host tooling and viz (see above).
for_each_warning_with_capacity::<N> sets the dedup capacity (default 20).
Overflowing it is not an error β deduplication just stops, and a dataset may be
reported twice.
The check subcommand does not print these under no_std: there is no sink to
print to, and PondError::CheckFailed carries no payload. Call
for_each_warning yourself as above.
The catalog cross-check, check_catalog, is not available in no_std β it
needs the serde catalog walk, which is std only. Run it from the host-side
binary described at the top of this page.