bevyengine/bevy · error · InvalidIntervalError

The resulting interval would be invalid (empty or with a NaN

Error message

The resulting interval would be invalid (empty or with a NaN endpoint)

What it means

InvalidIntervalError is returned whenever an operation would produce an empty or NaN-endpoint Interval. Interval::new(start, end) rejects start > end and any NaN argument; Interval::intersect rejects producing an empty result (disjoint intervals); the interval()/TryFrom<RangeInclusive> helpers and curve adaptors like zip (intersecting two curve domains) can surface it too. Intervals must satisfy start <= end with both endpoints finite/valid.

Source

Thrown at crates/bevy_math/src/curve/interval.rs:37

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(Reflect),
    reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
    all(feature = "serialize", feature = "bevy_reflect"),
    reflect(Serialize, Deserialize)
)]
pub struct Interval {
    start: f32,
    end: f32,
}

/// An error that indicates that an operation would have returned an invalid [`Interval`].
#[derive(Debug, Error)]
#[error("The resulting interval would be invalid (empty or with a NaN endpoint)")]
pub struct InvalidIntervalError;

/// An error indicating that spaced points could not be extracted from an unbounded interval.
#[derive(Debug, Error)]
#[error("Cannot extract spaced points from an unbounded interval")]
pub struct SpacedPointsError;

/// An error indicating that a linear map between intervals could not be constructed because of
/// unboundedness.
#[derive(Debug, Error)]
#[error("Could not construct linear function to map between intervals")]
pub(super) enum LinearMapError {
    /// The source interval being mapped out of was unbounded.
    #[error("The source interval is unbounded")]
    SourceUnbounded,

    /// The target interval being mapped into was unbounded.
    #[error("The target interval is unbounded")]

View on GitHub (pinned to 396ca72708)

Solutions

  1. Order endpoints before constructing: Interval::new(a.min(b), a.max(b)).
  2. Guard against NaN: check a.is_finite() && b.is_finite() before calling.
  3. For zip/intersect of possibly-disjoint ranges, check overlap first (max(starts) <= min(ends)) and handle the disjoint case explicitly instead of unwrapping.

Example fix

// before
let domain = Interval::new(t_max, t_min)?; // swapped -> InvalidIntervalError
let zipped = curve_a.zip(curve_b)?; // disjoint domains -> InvalidIntervalError

// after
let domain = Interval::new(t_min.min(t_max), t_min.max(t_max))?;
let zipped = if curve_a.domain().intersect(curve_b.domain()).is_ok() {
    Some(curve_a.zip(curve_b)?)
} else {
    None // no overlap: skip or report
};
Defensive patterns

Strategy: validation

Validate before calling

fn valid_interval(a: f32, b: f32) -> bool {
    a.is_finite() && b.is_finite() && a <= b
}

if valid_interval(start, end) {
    let interval = Interval::new(start, end)?;
}

Try / catch

let interval = match Interval::new(start, end) {
    Ok(iv) => iv,
    Err(InvalidIntervalError) => Interval::new(end, start)?, // swapped inputs: normalize order
};

Prevention

When it happens

Trigger: Interval::new(end, start) with swapped arguments; interval(a, b) where a > b; passing NaN (e.g. from 0.0/0.0); curve1.zip(curve2) on curves with disjoint domains; intersect(Interval::new(0.,1.)?, Interval::new(2.,3.)?) yielding empty.

Common situations: Computing domain endpoints from data where min/max were never ordered (single-element iterates, reversed loops); NaN leaking from trig or normalization math; zipping two animations whose time ranges don't overlap.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/9a05ccc79c46c19c. Report an issue: GitHub.