Sundo - Snapshot Undo/Redo
A flexible and efficient undo/redo library for Rust, with first-class support for persistent data structures.
Features
- Generic undo/redo for any cloneable type
- Optimized for persistent data structures (
im::Vector,im::HashMap, etc.) - Transaction support with automatic rollback (RAII)
- History limits (count-based and memory-based) to prevent unbounded memory growth
- In-place updates with
replace_current()for ephemeral state (navigation, UI state) - Serialization support via snapshot export/import
- Builder pattern for flexible configuration
- Type-safe and ergonomic API
- Zero-cost abstractions when features aren't used
Installation
[]
= "0.1.0"
Quick Start
Basic Usage
use ;
let mut undo_redo = new;
// Push initial state
undo_redo.push;
// Make changes
undo_redo.update;
undo_redo.update;
// Undo and redo
assert_eq!;
undo_redo.undo;
assert_eq!;
undo_redo.redo;
assert_eq!;
With Persistent Data Structures
use ;
use Vector;
let mut undo_redo = new;
undo_redo.push;
undo_redo.update;
undo_redo.update;
// No Rc overhead - direct storage with structural sharing
let current = undo_redo.present.unwrap;
assert_eq!;
With History Limits
use UndoRedoBuilder;
// Count-based limit
let mut undo_redo = new
.with_max_entries // Keep only last 100 states
.build;
// Memory-based limit
let mut undo_redo = new
.with_max_memory_mb // Keep up to 50 MB of history
.build;
// Combined limits (whichever is hit first)
let mut undo_redo = new
.with_max_entries
.with_max_memory_mb
.build;
// Automatically prunes oldest entries when limit exceeded
With Transactions
use ;
let mut undo_redo = new;
undo_redo.push;
// Or auto-rollback on error:
Ephemeral State Updates
For state changes that shouldn't be undoable (like navigation or UI state):
use ;
use ;
let mut undo_redo = new;
// ... add some todos (undoable) ...
// Change page without creating history entry
undo_redo.replace_current;
// Undo still works on todos, navigation stays at Settings
undo_redo.undo; // Reverts todo changes, keeps current_page
API Overview
Core Traits
Actions<T>- Standard undo/redo operations withRcwrappingPersistentActions<T>- Optimized for persistent data structures (noRc)
Core Types
UndoRedo<T>- Standard implementation for any cloneable typePersistentUndoRedo<T>- Optimized forim::types
Builder Pattern
UndoRedoBuilder<T>- Configure with capacity and limitsPersistentUndoRedoBuilder<T>- Builder for persistent variant
Transactions
ScopedTransaction- RAII transaction with auto-rollbackPersistentScopedTransaction- Transaction for persistent types
Memory Estimation
MemoryFootprint- Trait for memory-based history limits
Examples
See the demos/ directory for complete examples:
- basic-example - Comparison of standard vs persistent implementations
- persistent-example - Deep dive into persistent data structures
- scoped-transaction - Transaction patterns and error handling with RAII
- builder-pattern - History limits configuration
- memory-limits - Memory-based limits with MemoryFootprint trait
- mem-performance - Comprehensive memory performance testing and analysis
- serialization-example - Save/load history without serde
- delta-example - Compute diffs between history states
- todo_app - Full TUI todo application with undo/redo, transactions, save/load
Run demos with:
&&
&&
&&
&&
# etc.
Note: All demos use the library via a local path dependency:
[]
= { = "../.." }
This is a good pattern for your own projects during development.
Design Philosophy
Memory Efficiency
For standard types, sundo uses Rc<T> to minimize memory overhead when cloning is expensive. For persistent data structures (which already use structural sharing), sundo stores values directly without the Rc layer.
Persistent Data Structures
When using im::Vector, im::HashMap, etc., the library leverages their built-in structural sharing. This means history entries share most of their data, making undo/redo memory-efficient even for large collections.
Zero-Cost Abstractions
- Builder pattern only allocates when configured
- History limits only check when set
- Transaction overhead is minimal (single Option field)
Documentation
- Usage Guide - for additional info
Generate API documentation locally:
Use Cases
- we will find out...
License
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.