Skip to content

[Refactor] Convert Math Panics to Result for Public Helpers #85

@vmarcella

Description

@vmarcella

Overview

Replace panic! calls in public math functions with Result return types to allow callers to handle errors gracefully.

Current State

The math module contains several panics! throughout our math module:

Cross product dimension check:

// crates/lambda-rs/src/math/vector.rs:86-88
_ => {
  panic!("Cross product is only defined for 3 dimensional vectors.")
}

Axis unit vector check:

// crates/lambda-rs/src/math/matrix.rs:151-152
_ => {
  panic!("Axis must be a unit vector")
}

Determinant non-square check:

// crates/lambda-rs/src/math/matrix.rs:335
panic!("Cannot compute determinant of non-square matrix");

These panics will cause programs implementing our math functions to crash, not allowing users to handle these errors within their own code without preventing the stack from unwinding (Which is not ideal for users to have to do).

Scope

  • Define MathError enum with descriptive variants
  • Convert cross()1 to return Result<Self, MathError>
  • Convert rotate_matrix() to return Result<MatrixLike, MathError>
  • Convert determinant to return Result<f32, MathError>
  • Review other assert! macros in math modules for similar conversions (And perhaps add features to selectively enable them)
  • Update all current callers to handle Result returns

Proposed API

// crates/lambda-rs/src/math/error.rs (new file)

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MathError {
  /// Cross product requires exactly 3 dimensions.
  CrossProductDimension { actual: usize },
  /// Rotation axis must be a unit vector (one of [1,0,0], [0,1,0], [0,0,1]).
  InvalidRotationAxis { axis: [i32; 3] },
  /// Determinant requires a square matrix.
  NonSquareMatrix { rows: usize, cols: usize },
  /// Cannot normalize a zero-length vector.
  ZeroLengthVector,
}

impl std::fmt::Display for MathError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      MathError::CrossProductDimension { actual } => {
        write!(f, "Cross product requires 3D vectors, got {}D", actual)
      }
      MathError::InvalidRotationAxis { axis } => {
        write!(f, "Rotation axis {:?} is not a unit axis vector", axis)
      }
      MathError::NonSquareMatrix { rows, cols } => {
        write!(f, "Determinant requires square matrix, got {}x{}", rows, cols)
      }
      MathError::ZeroLengthVector => {
        write!(f, "Cannot normalize a zero-length vector")
      }
    }
  }
}

impl std::error::Error for MathError {}

Updated function signatures

// vector.rs
pub trait Vector {
  // ...
  fn cross(&self, other: &Self) -> Result<Self, MathError>;
  fn normalize(&self) -> Result<Self, MathError>;  // zero-length check
}

// matrix.rs
pub fn rotate_matrix<V, MatrixLike>(
  matrix_to_rotate: MatrixLike,
  axis: [f32; 3],
  angle_in_turns: f32,
) -> Result<MatrixLike, MathError>;

pub trait Matrix<V> {
  fn determinant(&self) -> Result<f32, MathError>;
}

Migrations

// Before
let normal = direction.cross(&up);

// After (internal code that knows inputs are valid)
let normal = direction.cross(&up)
  .expect("direction and up are both 3D vectors");

// Or propagate in user-facing code
let normal = direction.cross(&up)?;

Acceptance criteria

  • MathError enum with descriptive variants
  • impl std::error::Error for MathError
  • Vector::cross() returns Result<Self, MathError>
  • Vector::normalize() returns Result<Self, MathError> (zero-length check)
  • rotate_matrix() returns Result<MatrixLike, MathError>
  • Matrix::determinnant returns <Result<f32, MathError>`
  • Update tests
  • Update internal and example usage
  • Documentation updated to describe error conditions

Notes

  • assert_eq! used for dimension matching in dot(), add(), etc. MAY remain as debug assertions since mismatched dimensions indicate programmer error
  • Consider keeping debug_assert! for internal invariants while using Result for user-facing validation
  • Error messages MUST be actionable, describing what was expected vs. received when possible

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions