Skip to content

Conversation

@vmarcella
Copy link
Member

Summary

Refactors the math library to return Result types with descriptive errors instead of panicking. This allows users to handle math errors gracefully and makes the API more idiomatic Rust. The change aligns with the repository guidelines that state: "avoid using panic unless absolutely necessary and allow for the user handle errors whenever possible."

Related Issues

Changes

  • Add new MathError enum in crates/lambda-rs/src/math/error.rs with descriptive error variants for all fallible operations
  • Update Vector::cross() to return Result<Self, MathError> instead of panicking on dimension mismatches
  • Update Vector::normalize() to return Result<Self, MathError> instead of panicking on zero-length vectors
  • Update Matrix::determinant() to return Result<f32, MathError> instead of panicking on empty or non-square matrices
  • Update rotate_matrix() to return Result<MatrixLike, MathError> instead of panicking on invalid inputs
  • Simplify rotate_matrix() axis parameter from generic InputVector to concrete [f32; 3] array
  • Update examples (reflective_room.rs, textured_cube.rs) and internal usage (scene_math.rs) to handle the new Result types
  • Update tutorials (reflective-room.md, textured-cube.md) to reflect the API changes

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (updates to docs, specs, tutorials, or comments)
  • Refactor (code change that neither fixes a bug nor adds a feature)
  • Performance (change that improves performance)
  • Test (adding or updating tests)
  • Build/CI (changes to build process or CI configuration)

Affected Crates

  • lambda-rs
  • lambda-rs-platform
  • lambda-rs-args
  • lambda-rs-logging
  • Other:

Checklist

  • Code follows the repository style guidelines (cargo +nightly fmt --all)
  • Code passes clippy (cargo clippy --workspace --all-targets -- -D warnings)
  • Tests pass (cargo test --workspace)
  • New code includes appropriate documentation
  • Public API changes are documented
  • Breaking changes are noted in this PR description

Testing

Commands run:

cargo build --workspace
cargo test --workspace
cargo run --example reflective_room
cargo run --example textured_cube

Manual verification steps (if applicable):

  1. Run reflective_room example and verify rotation behavior is unchanged
  2. Run textured_cube example and verify cube renders correctly with rotation
  3. Verify error messages are descriptive when invalid inputs are provided

Screenshots/Recordings

N/A - No visual changes; API behavior remains the same for valid inputs.

Platform Testing

  • macOS
  • Windows
  • Linux

Additional Notes

Breaking Changes

The following public APIs now return Result types:

Function/Method Old Signature New Signature
Vector::cross() fn cross(&self, other: &Self) -> Self fn cross(&self, other: &Self) -> Result<Self, MathError>
Vector::normalize() fn normalize(&self) -> Self fn normalize(&self) -> Result<Self, MathError>
Matrix::determinant() fn determinant(&self) -> f32 fn determinant(&self) -> Result<f32, MathError>
rotate_matrix() fn rotate_matrix<...>(...) -> OutputMatrix fn rotate_matrix<...>(...) -> Result<MatrixLike, MathError>

Migration Guide

Users calling these functions will need to handle the Result:

// Before
let normal = vec_a.cross(&vec_b);
let unit = normal.normalize();

// After
let normal = vec_a.cross(&vec_b)?;  // or .expect("msg") / .unwrap()
let unit = normal.normalize()?;

New Error Types

The MathError enum provides detailed context for each failure case:

  • CrossProductDimension { actual } - Cross product requires 3D vectors
  • MismatchedVectorDimensions { left, right } - Vectors must have matching dimensions
  • InvalidRotationAxis { axis } - Rotation axis must be a unit axis vector
  • InvalidRotationMatrixSize { rows, cols } - Rotation requires a 4x4 matrix
  • NonSquareMatrix { rows, cols } - Operation requires square matrix
  • EmptyMatrix - Operation requires a non-empty matrix
  • ZeroLengthVector - Cannot normalize a zero-length vector

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the math library to return Result types instead of panicking, aligning with Rust best practices and the repository's guidelines to "avoid using panic unless absolutely necessary." The change converts four key math operations (Vector::cross, Vector::normalize, Matrix::determinant, and rotate_matrix) to return descriptive errors when given invalid inputs.

Changes:

  • Introduced comprehensive MathError enum with variants for all fallible math operations
  • Converted panic-based error handling to Result-based error handling for cross product, normalize, determinant, and matrix rotation operations
  • Updated all examples, tests, and documentation to handle the new Result types appropriately

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/lambda-rs/src/math/error.rs New error module with MathError enum and Display/Error trait implementations
crates/lambda-rs/src/math/mod.rs Exports the new error module and MathError type
crates/lambda-rs/src/math/vector.rs Updated cross() and normalize() to return Result types; updated tests
crates/lambda-rs/src/math/matrix.rs Updated determinant() and rotate_matrix() to return Result types; updated tests
crates/lambda-rs/src/render/scene_math.rs Updated compute_model_matrix to handle rotate_matrix Result
crates/lambda-rs/examples/textured_cube.rs Updated to handle rotate_matrix Result with expect()
crates/lambda-rs/examples/reflective_room.rs Updated to handle rotate_matrix Result with expect()
docs/tutorials/textured-cube.md Updated tutorial code samples and metadata to reflect API changes
docs/tutorials/reflective-room.md Updated tutorial code samples and metadata to reflect API changes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 346 to 352
2 => {
let a = self.at(0, 0);
let b = self.at(0, 1);
let c = self.at(1, 0);
let d = self.at(1, 1);
a * d - b * c
return Ok(a * d - b * c);
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement inside this match arm is redundant since the entire match expression is already being returned on line 344. The arm should simply be 2 => Ok(a * d - b * c), without the explicit return statement.

Copilot uses AI. Check for mistakes.
Comment on lines 95 to 152
pub fn rotate_matrix<
InputVector: Vector<Scalar = f32>,
ResultingVector: Vector<Scalar = f32>,
OutputMatrix: Matrix<ResultingVector> + Default + Clone,
V: Vector<Scalar = f32>,
MatrixLike: Matrix<V> + Default + Clone,
>(
matrix_to_rotate: OutputMatrix,
axis_to_rotate: InputVector,
matrix_to_rotate: MatrixLike,
axis_to_rotate: [f32; 3],
angle_in_turns: f32,
) -> OutputMatrix {
) -> Result<MatrixLike, MathError> {
let (rows, columns) = matrix_to_rotate.size();
assert_eq!(rows, columns, "Matrix must be square");
assert_eq!(rows, 4, "Matrix must be 4x4");
assert_eq!(
axis_to_rotate.size(),
3,
"Axis vector must have 3 elements (x, y, z)"
);
if rows != columns {
return Err(MathError::NonSquareMatrix {
rows,
cols: columns,
});
}
if rows != 4 {
return Err(MathError::InvalidRotationMatrixSize {
rows,
cols: columns,
});
}

let angle_in_radians = turns_to_radians(angle_in_turns);
let cosine_of_angle = angle_in_radians.cos();
let sin_of_angle = angle_in_radians.sin();

let _t = 1.0 - cosine_of_angle;
let x = axis_to_rotate.at(0);
let y = axis_to_rotate.at(1);
let z = axis_to_rotate.at(2);

let mut rotation_matrix = OutputMatrix::default();

let rotation = match (x as u8, y as u8, z as u8) {
(0, 0, 0) => {
// No rotation
return matrix_to_rotate;
}
(0, 0, 1) => {
// Rotate around z-axis
[
[cosine_of_angle, sin_of_angle, 0.0, 0.0],
[-sin_of_angle, cosine_of_angle, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
}
(0, 1, 0) => {
// Rotate around y-axis
[
[cosine_of_angle, 0.0, -sin_of_angle, 0.0],
[0.0, 1.0, 0.0, 0.0],
[sin_of_angle, 0.0, cosine_of_angle, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
}
(1, 0, 0) => {
// Rotate around x-axis
[
[1.0, 0.0, 0.0, 0.0],
[0.0, cosine_of_angle, sin_of_angle, 0.0],
[0.0, -sin_of_angle, cosine_of_angle, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
}
_ => {
panic!("Axis must be a unit vector")
}
let mut rotation_matrix = MatrixLike::default();
let [x, y, z] = axis_to_rotate;

let rotation = if axis_to_rotate == [0.0, 0.0, 0.0] {
return Ok(matrix_to_rotate);
} else if axis_to_rotate == [0.0, 0.0, 1.0] {
// Rotate around z-axis
[
[cosine_of_angle, sin_of_angle, 0.0, 0.0],
[-sin_of_angle, cosine_of_angle, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
} else if axis_to_rotate == [0.0, 1.0, 0.0] {
// Rotate around y-axis
[
[cosine_of_angle, 0.0, -sin_of_angle, 0.0],
[0.0, 1.0, 0.0, 0.0],
[sin_of_angle, 0.0, cosine_of_angle, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
} else if axis_to_rotate == [1.0, 0.0, 0.0] {
// Rotate around x-axis
[
[1.0, 0.0, 0.0, 0.0],
[0.0, cosine_of_angle, sin_of_angle, 0.0],
[0.0, -sin_of_angle, cosine_of_angle, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
} else {
return Err(MathError::InvalidRotationAxis { axis: [x, y, z] });
};
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new error cases for rotate_matrix (InvalidRotationAxis and InvalidRotationMatrixSize) lack test coverage. Consider adding tests that verify these error conditions are properly returned when invalid inputs are provided, such as testing with a non-unit axis vector like [1.0, 1.0, 0.0] or a non-4x4 matrix.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +92
fn cross(&self, other: &Self) -> Result<Self, MathError> {
let left_size = self.as_ref().len();
let right_size = other.as_ref().len();
if left_size != right_size {
return Err(MathError::MismatchedVectorDimensions {
left: left_size,
right: right_size,
});
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MismatchedVectorDimensions error case is not covered by tests. Consider adding a test that verifies this error is returned when cross product is called with vectors of different dimensions, such as a 2D vector and a 3D vector.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants