check-bounds-safety by r3bl-org
Apply type-safe bounds checking patterns using Index/Length types instead of usize. Use when working with arrays, buffers, cursors, viewports, or any code that handles indices and lengths.
Content & Writing
462 Stars
30 Forks
Updated Dec 7, 2025, 12:40 AM
Why Use This
This skill provides specialized capabilities for r3bl-org's codebase.
Use Cases
- Developing new features in the r3bl-org repository
- Refactoring existing code to follow r3bl-org standards
- Understanding and working with r3bl-org's codebase structure
Install Guide
2 steps- 1
Skip this step if Ananke is already installed.
- 2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 465 Lines
Total Files 2
Total Size 14.9 KB
License Apache-2.0
---
name: check-bounds-safety
description: Apply type-safe bounds checking patterns using Index/Length types instead of usize. Use when working with arrays, buffers, cursors, viewports, or any code that handles indices and lengths.
---
# Type-Safe Bounds Checking
## When to Use
- Working with array access or buffer operations
- Implementing cursor positioning logic (text editors, terminal emulators)
- Handling viewport rendering and scrolling
- Dealing with 0-based indices vs 1-based lengths
- Validating range boundaries
- Converting VT-100 ranges to Rust ranges
- Before creating commits with bounds-sensitive code
- When user asks about "bounds checking", "type safety", "off-by-one errors", etc.
## The Problem
Raw `usize` values are ambiguous and error-prone:
```rust
// ❌ Bad - What is `x`? Index or length?
let x = 10_usize;
if x < length { // Off-by-one error waiting to happen
buffer[x]
}
// Is this an index (0-based) or a length (1-based)?
// The type system can't help us!
```
## The Solution
Use type-safe wrappers from `tui/src/core/units/bounds_check/`:
```rust
// ✅ Good - Types make it clear
use r3bl_tui::{idx, len, ArrayBoundsCheck};
let index = idx(10); // Clearly an index (0-based)
let length = len(100); // Clearly a length (1-based)
if index.overflows(length) {
// Safely caught! Can't accidentally compare incompatible types
}
```
## Core Principles
Follow these principles when working with indices and lengths:
1. **Use Index types (0-based)** instead of `usize`
- `RowIndex`, `ColIndex`, `Index`
- Construct with `row()`, `col()`, `idx()`
2. **Use Length types (1-based)** instead of `usize`
- `RowHeight`, `ColWidth`, `Length`
- Construct with `height()`, `width()`, `len()`
3. **Type-safe comparisons**
- Cannot compare `RowIndex` with `ColWidth` (compile error!)
- Prevents category errors like "is row 5 < width 10?"
4. **Use `.is_zero()` for zero checks**
- Instead of `== 0`
- More idiomatic with newtype wrappers
5. **Distinguish navigation from measurement**
- **Navigation** (`index - offset → index`): Moving backward in position space
- **Measurement** (`index.distance_from(other) → length`): Calculating distance between positions
- Use `-` for cursor movement, use `distance_from()` for calculating spans
## Common Imports
```rust
use std::ops::Range;
use r3bl_tui::{
// Traits
ArrayBoundsCheck, CursorBoundsCheck, ViewportBoundsCheck,
RangeBoundsExt, RangeConvertExt, IndexOps, LengthOps,
// Status enums
ArrayOverflowResult, CursorPositionBoundsStatus,
RangeValidityStatus, RangeBoundsResult,
// Type constructors
col, row, width, height, idx, len,
// Terminal delta types (relative cursor movement)
TermRowDelta, TermColDelta, term_row_delta, term_col_delta,
};
```
## Quick Pattern Reference
| Use Case | Trait | Key Method | When to Use |
| ----------------------- | --------------------- | -------------------------------------------- | ----------------------------------------------------------- |
| **Array access** | `ArrayBoundsCheck` | `index.overflows(length)` | Validating `buffer[index]` access (`index < length`) |
| **Cursor positioning** | `CursorBoundsCheck` | `length.check_cursor_position_bounds(pos)` | Text editing where cursor can be at end (`index <= length`) |
| **Viewport visibility** | `ViewportBoundsCheck` | `index.check_viewport_bounds(start, size)` | Rendering optimization (is content on-screen?) |
| **Range validation** | `RangeBoundsExt` | `range.check_range_is_valid_for_length(len)` | Iterator bounds, algorithm parameters |
| **Range membership** | `RangeBoundsExt` | `range.check_index_is_within(index)` | VT-100 scroll regions, text selections |
| **Range conversion** | `RangeConvertExt` | `inclusive_range.to_exclusive()` | Converting VT-100 ranges for Rust iteration |
| **Relative movement** | `TermRowDelta`/`TermColDelta` | `TermRowDelta::new(n)` returns `Option` | ANSI cursor movement preventing CSI zero bug |
## Detailed Examples
### Example 1: Array Bounds Checking
**Use `ArrayBoundsCheck` when validating buffer access.**
```rust
use r3bl_tui::{idx, len, ArrayBoundsCheck, ArrayOverflowResult};
let buffer_length = len(100);
let index = idx(50);
match index.overflows(buffer_length) {
ArrayOverflowResult::Within => {
// Safe to access: buffer[50]
let value = buffer[index.value()];
}
ArrayOverflowResult::Overflows => {
// Out of bounds! Handle error
eprintln!("Index {} overflows buffer length {}", index, buffer_length);
}
}
```
**Mathematical law:**
- For valid access: `0 <= index < length`
- Or equivalently: `index < length` (since Index is always >= 0)
### Example 2: Cursor Position Bounds
**Use `CursorBoundsCheck` for text cursor positioning.**
```rust
use r3bl_tui::{idx, len, CursorBoundsCheck, CursorPositionBoundsStatus};
let text_length = len(10); // Text has 10 characters
let cursor = idx(10); // Cursor at position 10 (after last char)
match text_length.check_cursor_position_bounds(cursor) {
CursorPositionBoundsStatus::Within => {
// Valid! Cursor CAN be at position 10 (after char 9)
// User can insert text here
}
CursorPositionBoundsStatus::Overflows => {
// Invalid cursor position
}
}
```
**Mathematical law:**
- For valid cursor: `0 <= position <= length`
- **Note:** Cursor CAN be at `length` (after the last character)
**Key difference from array access:**
- Array access: `index < length` (strict inequality)
- Cursor position: `index <= length` (includes equality)
### Example 3: Viewport Visibility Check
**Use `ViewportBoundsCheck` to optimize rendering.**
```rust
use r3bl_tui::{idx, len, ViewportBoundsCheck};
let line_index = idx(150); // Line 150 in document
let viewport_start = idx(100); // Viewport starts at line 100
let viewport_size = len(50); // Viewport shows 50 lines
if line_index.check_viewport_bounds(viewport_start, viewport_size) {
// Line 150 is visible (100 <= 150 < 150)
// Render this line
} else {
// Line is off-screen, skip rendering
}
```
**Mathematical law:**
- Visible if: `viewport_start <= index < viewport_start + viewport_size`
### Example 4: Range Validation
**Use `RangeBoundsExt` to validate range boundaries.**
```rust
use r3bl_tui::{len, RangeBoundsExt, RangeValidityStatus};
let buffer_length = len(100);
let range = 10..50; // Want to process elements 10-49
match range.check_range_is_valid_for_length(buffer_length) {
RangeValidityStatus::Valid => {
// Range is valid for this buffer
for i in range {
process(buffer[i]);
}
}
RangeValidityStatus::Invalid(reason) => {
eprintln!("Invalid range: {}", reason);
}
}
```
### Example 5: Range Membership
**Use `RangeBoundsExt` to check if index is within a range.**
```rust
use r3bl_tui::{idx, RangeBoundsExt};
// VT-100 scroll region: lines 5-15
let scroll_region = 5..=15; // Inclusive range
let cursor_row = idx(10);
if scroll_region.check_index_is_within(cursor_row) {
// Cursor is within scroll region
// Apply scroll behavior
} else {
// Cursor outside scroll region
}
```
### Example 6: Range Conversion
**Use `RangeConvertExt` to convert inclusive to exclusive ranges.**
```rust
use r3bl_tui::RangeConvertExt;
// VT-100 uses inclusive ranges: 1..=10 means lines 1 through 10
let vt100_range = 1..=10;
// Rust iterators use exclusive ranges: 1..11
let rust_range = vt100_range.to_exclusive();
// Now can use in Rust iteration
for line in rust_range {
process_line(line);
}
```
### Example 7: Navigation vs Measurement
**Use `-` for navigation (moving cursor), `distance_from()` for measurement (calculating spans).**
```rust
use r3bl_tui::{row, height, RowIndex, RowHeight};
// Navigation: Move cursor backward by offset (returns RowIndex).
let cursor_pos = row(10);
let new_pos = cursor_pos - row(3); // row(7) - moved 3 positions back
// Uses saturating subtraction: row(2) - row(5) = row(0), not overflow
// Measurement: Calculate distance between two positions (returns RowHeight).
let start = row(5);
let end = row(15);
let distance: RowHeight = end.distance_from(start); // height(10) - 10 rows apart
// Panics if start > end (negative distance)
```
**When to use which:**
- Moving cursor up/down/left/right → `-` operator
- Calculating scroll amount, viewport span, selection size → `distance_from()`
### Example 8: Terminal Cursor Movement (Make Illegal States Unrepresentable)
**Use `TermRowDelta`/`TermColDelta` for relative cursor movement in ANSI sequences.**
The CSI zero problem: ANSI cursor movement commands interpret parameter 0 as 1:
- `CSI 0 A` (`CursorUp` with n=0) moves cursor **1 row up**, not 0
- `CSI 0 C` (`CursorForward` with n=0) moves cursor **1 column right**, not 0
**Solution:** `TermRowDelta` and `TermColDelta` wrap `NonZeroU16` internally, making zero-valued
deltas **impossible to represent**. Construction is fallible:
```rust
use r3bl_tui::{TermRowDelta, TermColDelta, CsiSequence};
use std::io::Write;
// Calculate cursor movement from position on 80-column terminal.
let position: u16 = 240; // 240 chars from start
let term_width: u16 = 80;
// Fallible construction - must handle the None case.
// For position 240: rows = 3 (Some), cols = 0 (None).
if let Some(delta) = TermRowDelta::new(position / term_width) {
// delta is guaranteed non-zero, safe to emit
term.write_all(CsiSequence::CursorDown(delta).to_string().as_bytes())?;
}
if let Some(delta) = TermColDelta::new(position % term_width) {
// This branch is NOT taken for position 240 (cols = 0)
// Zero cannot be represented, so the bug is prevented at the type level!
term.write_all(CsiSequence::CursorForward(delta).to_string().as_bytes())?;
}
```
**Using the ONE constant for common case:**
```rust
use r3bl_tui::{TermRowDelta, TermColDelta, CsiSequence};
// For the common case of moving exactly 1 row/column, use the ONE constant.
// This avoids the need for fallible construction or unwrap().
let up_one = CsiSequence::CursorUp(TermRowDelta::ONE);
let right_one = CsiSequence::CursorForward(TermColDelta::ONE);
```
**Mathematical law:**
- Zero deltas **cannot exist** - prevented at compile time by `NonZeroU16` wrapper
- `new()` returns `None` for zero, `Some(delta)` for non-zero
**Key difference from absolute positioning:**
- `TermRow`/`TermCol`: 1-based absolute coordinates (for `CursorPosition`)
- `TermRowDelta`/`TermColDelta`: Relative movement amounts (for `CursorUp/Down/Forward/Backward`)
## Decision Trees
See the accompanying `decision-trees.md` file for flowcharts showing which trait to use for
each scenario.
## Detailed Reference
For comprehensive documentation, decision trees, and more examples, see:
[`tui/src/core/units/bounds_check/mod.rs`](tui/src/core/units/bounds_check/mod.rs)
This module contains:
- Complete API documentation
- Mathematical laws for each trait
- Visual decision trees
- Edge case handling
- Performance notes
## Common Mistakes
### ❌ Mistake 1: Using raw usize
```rust
// Bad - ambiguous types
let index: usize = 10;
let length: usize = 100;
if index < length { // Works, but no type safety
// ...
}
```
**Fix:**
```rust
// Good - clear types
let index = idx(10);
let length = len(100);
if !index.overflows(length) { // Type-safe!
// ...
}
```
### ❌ Mistake 2: Array bounds used for cursor
```rust
// Bad - cursor can be at end!
let cursor = idx(10);
let text_length = len(10);
if cursor.overflows(text_length) { // Wrong! Cursor at end is valid
return Err("Invalid cursor");
}
```
**Fix:**
```rust
// Good - cursor bounds check
if text_length.check_cursor_position_bounds(cursor) == CursorPositionBoundsStatus::Overflows {
return Err("Invalid cursor");
}
```
### ❌ Mistake 3: Comparing incompatible types
```rust
// Bad - this won't compile (good!)
let row = row(5);
let width = width(10);
if row < width { // Compile error! Can't compare RowIndex with ColWidth
// ...
}
```
This is actually GOOD - the type system prevents nonsensical comparisons!
### ❌ Mistake 4: Using `-` when you need distance
```rust
// Bad - using subtraction to calculate distance.
// This returns RowIndex, not RowHeight!
let current_row = row(5);
let target_row = row(15);
let rows_to_scroll = target_row - current_row; // Returns row(10), a position!
```
**Fix:**
```rust
// Good - use distance_from() for measurement.
let rows_to_scroll: RowHeight = target_row.distance_from(current_row); // height(10)
```
### ❌ Mistake 5: Emitting CSI zero for cursor movement
```rust
// Bad - CSI 0 C moves 1 column right, not 0!
// This won't even compile now - CursorForward requires TermColDelta, not u16!
let cols = position % term_width; // Could be 0!
let seq = CsiSequence::CursorForward(cols); // Compile error!
```
**Fix:**
```rust
// Good - use fallible delta construction (zero is unrepresentable)
if let Some(delta) = TermColDelta::new(position % term_width) {
// delta is guaranteed non-zero
term.write_all(CsiSequence::CursorForward(delta).to_string().as_bytes())?;
}
// No sequence emitted when cols = 0 (correct behavior - branch not taken)
```
## Reporting Results
When applying bounds checking:
- ✅ All bounds checked with types → "Bounds safety verified with type-safe checks!"
- 🔧 Converted raw usize to Index/Length types → Report conversions made
- 📝 Added bounds checks → List where checks were added
## Supporting Files in This Skill
This skill includes additional reference material:
- **`decision-trees.md`** - Visual decision trees and flowcharts for choosing the right bounds checking approach: main decision tree (which trait?), array vs cursor bounds comparison, index vs length visual diagrams, viewport visibility flowchart, range validation flowchart, comparison table, edge case reference, and quick reference card. **Read this when:**
- Not sure which trait to use → Main decision tree
- Array access vs cursor positioning confusion → Visual comparison diagrams
- Viewport visibility logic → Viewport flowchart
- Range validation → Range validation flowchart
- Edge cases (empty arrays, cursor at end, zero-sized viewport) → Edge cases section
- Quick lookup of which method for which scenario → Comparison table
## Related Skills
- `check-code-quality` - Includes testing bounds-checking code
- `write-documentation` - For documenting bounds-checking logic
## Related Commands
No dedicated command, but used throughout the codebase for safe index/length handling.
## Additional Resources
- Main implementation: `tui/src/core/units/bounds_check/mod.rs`
- Type definitions: `tui/src/core/units/`
- Examples in tests: `tui/src/core/units/bounds_check/tests/`
- Terminal delta types: `tui/src/core/coordinates/vt_100_ansi_coords/term_row_delta.rs` and `term_col_delta.rs`
Name Size