Wado

WEP: Lenient String Parsing (LenientFromStr)

Context

Human-supplied strings become typed values throughout a program: CLI arguments (core:cli::args()), environment variables, config entries, query strings, compile-time parameters (Compile-Time Parameters, v2). Being human-typed, they carry surface variation a strict parser rejects — casing, alternate spellings (1 / 0 for a boolean), radix prefixes, digit separators.

core:prelude already has FromStr, but it is strict by design: it backs hot, machine-facing paths (router path segments, the JSON deserializer's from_str_range) where "TRUE" or "0x2A" must be rejected. Loosening it would break those.

So a parallel, forgiving conversion is missing. core:cli::args() returns a raw List<String>; every caller parses by hand.

Decision

Add LenientFromStr to core:prelude (auto-imported), the forgiving sibling of FromStr.

pub trait LenientFromStr {
    type Err;
    fn from_str_lenient(s: &String) -> Result<Self, Self::Err>;
}

A Separate Trait, Not FromStr or TryFrom<&String>

Strictness and leniency are independent capabilities — a wire-format type wants only strict FromStr, a "human duration" only lenient parsing — so they are separate traits. Against the general TryFrom<&String> (Conversion Traits), LenientFromStr is the named contract: the name promises forgiving, human-friendly spellings.

Leniency Contract

An impl accepts the common human-equivalent spellings for its type (see built-ins) and never panics — an undenotable value is Err. Leniency forgives spelling, not meaning: "0x2A"42, "forty-two"Err.

It does not touch whitespace. from_str_lenient does not trim, so surrounding whitespace yields Err; trimming is the caller's concern (s.trim(), or a consumer-level policy — e.g. compile-time params trims each override before converting). Keeping the conversion faithful avoids silently discarding input and resolves whitespace-significant types (char, String) uniformly.

Built-In Implementations

Type Accepted
String Any string (identity)
char Exactly one Unicode scalar
i8..i128 Decimal, or 0x / 0o / 0b (case-insensitive); optional leading sign
u8..u128 As integers, but unsigned (a leading - is Err)
f32, f64 Decimal with optional e exponent; nan / inf / infinity / ±inf (case-insensitive)
bool true / false / 1 / 0 (case-insensitive)

Future WEPs may extend the set (e.g. core:temporal multi-format dates).

No Blanket Impl (Yet)

A blanket impl<T: FromStr> LenientFromStr for T would drop the per-type boilerplate but overlaps the bool impl. Wado grants concrete-over-general priority only for variadic impls (Variadic Type Parameters); general specialization is unadopted (soundness unsettled — research-from-into-framework.md). So built-in impls stay explicit; revisit if specialization lands.

User Types

A user type implements LenientFromStr directly, independently of FromStr (either, both, or neither). Newtypes inherit it from their base type — type Port = u16 parses like u16 with no extra impl.

Consumers

Implementation Strategy

Consequences

Future Extensions