scan_fmt
A lightweight scanf()-style macro for parsing typed values out of strings and stdin in Rust.
Repository Health
Technical Analysis
scan_fmt provides a simple, familiar scanf()-like interface for extracting structured data from strings or standard input in Rust. Instead of hand-rolling split/parse chains, developers write a compact format string with tokens such as {d} for decimals, {x} for hex, {f} for floats, {[…]} for character-class patterns, and {/…/} for full regex captures, and get back a typed tuple or Result.
The crate ships two macro families: scan_fmt!/scanln_fmt! which return a Result and surface parse failures explicitly, and scan_fmt_some!/scanln_fmt_some! which return per-field Option values in the style of the original pre-1.0 API. It is #![no_std] with an optional std feature for stdin helpers and an optional regex feature for the {/…/} token, keeping the dependency footprint minimal for constrained environments.
What You Get
- scan_fmt! and scanln_fmt! macros returning a Result<(T1, T2, …), ScanError> for strict, fail-fast parsing
- scan_fmt_some! and scanln_fmt_some! macros returning per-field Option<T> values for lenient, partial-match parsing
- Format tokens for decimal ({d}), hex ({x}, with optional 0x prefix), float ({f}), free text ({}), and end-of-line anchors ({e})
- Character-class matching with {[…]}, including negation (^) and ranges (-), for custom token boundaries
- Optional regex-powered {/…/} tokens (behind the regex feature) for capturing arbitrary patterns, including single capture groups
- Width-limited tokens (e.g. {2d}, {3x}) to cap how many characters a field consumes
- #![no_std] core with opt-in std and regex features, so it can be used in constrained or embedded contexts
Common Use Cases
- Parsing structured lines from CLI prompts or interactive stdin input (e.g. “12-34” or “hello 0x1f 99”)
- Extracting fields from simple, line-oriented log or config formats without writing a full parser
- Quick-and-dirty coordinate, version, or ID extraction from strings in scripts, test fixtures, or small tools
- Replacing verbose split() + parse::<T>() chains with a single declarative format string
Under The Hood
Architecture: scan_fmt is a two-module crate: lib.rs defines the four public macros (scan_fmt!, scan_fmt_some!, scanln_fmt!, scanln_fmt_some!) plus a scan_fmt_help! dispatch macro that wraps or unwraps parsed tokens depending on whether the caller wants a Result or Option, and parse.rs (667 lines) implements the actual tokenizer/scanner as a hand-written character-by-character state machine over a VecScanner (a Vec<char> cursor with position and width-limit tracking). The core entry point parse::scan(input_string, format) walks the format string, dispatches to token-specific scan functions (scan_dec10, scan_hex16, scan_float, scan_pattern, scan_regex, scan_nonws_or_end), and returns an iterator of matched substrings that the macros then convert to typed values via parse::<T>() or from_str_radix. There is no separate lexer/parser split or AST — format-string interpretation and input scanning happen in the same pass.
Tech Stack: Pure Rust, #![no_std] at the core with two additive Cargo features: std (enables stdin()-reading helpers for scanln_fmt!/scanln_fmt_some!) and regex (pulls in the regex crate, version “1”, to power the optional {/.../} token), both enabled by default. No other runtime dependencies. The crate targets stable Rust via macro_rules! only — no proc-macros or build scripts — which keeps compile times low and the dependency graph minimal.
Code Quality: Tests live inline in lib.rs (12 #[test] functions) and parse.rs (14 #[test] functions) covering the primary token types, width limiting, hex/decimal/float edge cases, skip-assignment ({*...}), regex post-matching, and IP-address parsing via FromStr. Error reporting is a single opaque ScanError(String) wrapper with ad-hoc string messages (e.g. “internal u8”, “match::none”) rather than a structured error enum, so callers can display but not programmatically branch on failure kind. Naming is terse and consistent with the scanf() lineage (scan_dec10, scan_hex16) but sparsely documented at the private-function level; the crate-level doc comment carries most of the explanatory burden.
API Design: The public surface is deliberately small — four macros and one error type — and mirrors C’s scanf() token vocabulary, which makes the format-string mini-language immediately familiar to anyone who has used sscanf(). The Result-vs-Option split (scan_fmt! vs scan_fmt_some!) gives callers an explicit choice between fail-fast and partial-match semantics without needing two different call sites. The main ergonomic cost is that format strings are unchecked at compile time: a mismatch between the number of {} tokens and the number of typed arguments silently produces None/default values rather than a compiler error, which the README calls out explicitly as a known limitation.