v2: add NonZeroF*, FiniteF*, NonSubnormalF* class-based float niche types - #3
Merged
Conversation
…e types Implements issue #1. Adds six class-based float niche types (each for f32 and f64) via the existing (anchor, validity-predicate) model: - NonZeroF32/64 — reject zero as a *class* (+0.0 and -0.0); anchor +0.0 bits. Distinct from bit-exact NonValueF32<0x0000_0000>, which forbids only +0.0. - FiniteF32/64 — reject non-finite (NaN and ±inf); anchor a NaN pattern. Cannot hold NaN, so gets total Ord/Eq/Hash (= NonNan ∩ NonInf). - NonSubnormalF32/64 — reject subnormals; anchor a subnormal pattern. FiniteF* use impl_total_ord!; NonZeroF*/NonSubnormalF* use impl_partial_ord! (they can still hold NaN). The niche_float_class! macro's `reject` clause is generalized from a bare method ident (`value.$m()`) to a predicate closure, since NonZero (`v == 0.0`) and Finite (`!v.is_finite()`) have no single-method form. Existing NonNan/NonInf invocations are updated to the closure form; behavior is unchanged. Per-type tests cover class rejection, round-trip, and Option<T> niche size. Verified: cargo test --all-features, no_std build, clippy -D warnings, rustdoc -D warnings, and cargo +nightly miri test --all-features (all clean). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- int: gate `u16 -> NonMaxUsize` / `i16 -> NonMaxIsize` off 16-bit targets, where the source is not strictly narrower than usize/isize and would widen to the forbidden MAX, passing 0 to NonZero::new_unchecked (UB) through a safe From. Add a const size-ordering assert in widen_prim! as a compile-time backstop against future same-width pairs. - docs: correct the "always by bit pattern" claim. Bit-exact types reject by pattern; class-based types classify by value but anchor on a rejected-class pattern, sound iff the anchor == itself (+0.0 for NonZero, is_nan() for NonNan). Update float/lib module docs and README. - float serde: document that bit identity (NaN payload, signed zero) survives only on IEEE-bit-preserving formats; add a signed-zero / NaN-payload test. - tests: exercise `primitive & niche` bitand with distinct operands; add negative-subnormal rejection; dedupe the NonNan/Finite total-order+hash contract into shared helpers; gate std-only tests behind cfg(feature = "std") and run `cargo test --no-default-features` in CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1.
Adds the three class-based float niche families deferred from v1, each for
f32andf64, all built on the crate's existing (anchor bit-pattern, validity predicate) model — oneniche_float_class!invocation apiece plus anOrd-capability choice.New types
NonZeroF32/NonZeroF64+0.0and-0.0)+0.0bitsNaN)FiniteF32/FiniteF64NaNand ±inf)NaNpatternNaN)NonSubnormalF32/NonSubnormalF64NaN)NonZeroF*rejecting zero as a class is deliberately distinct from the bit-exactNonValueF32<0x0000_0000>, which forbids only+0.0and leaves-0.0valid — that footgun is exactly why the value-class type earns its own name.FiniteF*=NonNan* ∩ NonInf*; because it can never holdNaN, it gets a totalOrd/Eq/Hash(same-0.0-normalizedHashasNonNan*).One design note (small deviation from the issue's "no new machinery")
The issue assumed each type is just a macro invocation. The existing
niche_float_class!tookreject = <method-ident>and calledvalue.$method(), butNonZero(v == 0.0) andFinite(!v.is_finite()) have no single-method predicate. So the macro'srejectclause is generalized from a bare method ident to a predicate closure; the existingNonNan*/NonInf*invocations are updated to the closure form with unchanged behavior (covered by the existing tests). No other machinery changed.Acceptance criteria
niche_float_class!with the specified anchorsFiniteF*useimpl_total_ord!; the other four useimpl_partial_ord!Option<T>size assertionVerification (all clean, run locally)
cargo test --all-features— 28 tests + doctest passcargo build --no-default-features(no_std) — buildscargo clippy --all-features --all-targets -- -D warnings— cleanRUSTDOCFLAGS="-D warnings" cargo doc --all-features— no broken linkscargo +nightly miri test --all-features— clean (28 tests + doctest)Out of scope
Fatter niches that reclaim an entire forbidden class as multi-variant enum room — those need unstable
rustc_layout_scalar_valid_rangeand violate the crate's stable-only constraint.🤖 Generated with Claude Code