scan_fmt

A lightweight scanf()-style macro for parsing typed values out of strings and stdin in Rust.

Library
Cargo
v0.2.6
43stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
23/100Needs Attention
Development Activity0
Maintenance0
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture58
Code Quality62
Innovation55
Learning Curve78

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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search