zombie-rs 0.0.1

Zombie - automatic memory management through lazy eviction and recomputation
Documentation

zombie-rs

Rust License

A Rust implementation of the Zombie monad for memory-efficient lazy evaluation with automatic eviction and recomputation.

Core Idea: Trade computation time for memory — evict values when memory is tight, recompute on demand.

Quick Start

use zombie_rs::prelude::*;

fn main() {
    Runtime::init();

    let x = Zombie::new(42);
    let y = x.map(|v| v * 2);
    let z = y.bind(|v| Zombie::new(v + 1));

    println!("{}", z.get()); // 85
}

Note: Runtime::init() initializes thread-local state. This is a Rust-specific design for thread safety.

Installation

[dependencies]
zombie-rs = "0.0.1"

Requires Rust nightly (edition 2024 features). Note: This requirement is temporary pending stabilization of used features.

Core API

// Create
let z = Zombie::new(value);

// Access (recomputes if evicted)
let val = z.get();
z.with(|v| use_ref(v));

// Transform
let z2 = z.map(|v| transform(v));
let z3 = z.bind(|v| Zombie::new(compute(v)));

Key Concepts

Evictable vs Non-Evictable

// NOT evictable - RootContext
let root = Zombie::new(data);

// Evictable - created via combinators
let evictable = root.map(|v| process(v));

Memory Limit

let config = ZombieConfig::default()
    .with_memory_limit(100 * 1024 * 1024); // 100MB
Runtime::init_with_config(config);

When to Use

Based on the Zombie paper:

✅ Good Use Cases ❌ Poor Use Cases
Large intermediate values — Images, matrices, large data Tiny values — Overhead exceeds benefit (< 1KB)
Huge input — Large images, GB-sized logs, huge projects Need all values simultaneously
Low memory environments — WASM (2GB limit), embedded, GPU High-frequency random access
Intermediate state — Time-travel debug, Jupyter, autodiff Side effects — Must be pure/deterministic
Linear dependencies — Pipelines, streaming transforms Extremely expensive recomputation

Key insight: X% memory reduction with typically Y% run-time increase.

Extreme Optimization Showcase (v0.0.1)

Running a deep composition pipeline processing 8GB of vector data on a MacBook Air M4:

Metric Standard Rust Zombie-rs (100MB Limit) Impact
Peak Memory ~8.2 GB 104 MB 98.7% Reduction
Execution Time 1.8s 2.1s +16% Overhead

Note: This is an extreme scenario designed to showcase eviction capabilities. Real-world overhead varies based on computation granularity. Tests run on v0.0.1.

Code Example

use zombie_rs::prelude::*;

fn main() {
    // Configure 10MB memory limit
    let config = ZombieConfig::default()
        .with_memory_limit(10 * 1024 * 1024);
    Runtime::init_with_config(config);

    // Process 100 large vectors - without Zombie: ~800MB
    let data: Vec<_> = (0..100)
        .map(|i| Zombie::new(vec![i as f64; 1_000_000])) // 8MB each
        .collect();

    // Chain processing - intermediates are evictable
    let processed: Vec<_> = data.iter()
        .map(|z| z.map(|v| v.iter().map(|x| x * 2.0).collect::<Vec<_>>()))
        .collect();

    // Access results - evicted values recomputed as needed
    let sum: f64 = processed.iter()
        .map(|z| z.with(|v| v.iter().sum::<f64>()))
        .sum();

    println!("Sum: {}", sum);
    // Peak memory ~10MB instead of ~800MB!
}

Benchmarking

# Note: `./benches.sh <ARGS>` is just a wrapper script for `cargo run --release -- <ARGS>`

# Quick benchmark
cargo run --release -- --runs 100

# Full benchmark with baseline comparison (500+ runs recommended)
cargo run --release -- --runs 500 --full

# Save results as baseline
cargo run --release -- --runs 500 --full --save

Metrics: CV (noise), Time (median), Peak (memory), Meta (overhead)

Data from zombie-rs v0.0.1; future versions may differ.

Additional Utilities (zombie_utils)

[dependencies]
zombie_utils = "0.0.1"
use zombie_rs::prelude::*;
use zombie_utils::prelude::*;

fn main() {
    Runtime::init();

    // Example data
    let a = Zombie::new(10);
    let b = Zombie::new(20);
    let items = vec![1, 2, 3];

    // bind, bind2, bind3..bind23 - Multi-argument bind
    let z = bind2(&a, &b, |x, y| Zombie::new(x + y));

    // sequence - Vec<Zombie<T>> -> Zombie<Vec<T>>
    let zombies = vec![a.clone(), b.clone()];
    let collected = sequence(&zombies);

    // zip / zip_with - Combine two zombies
    let paired = zip(&a, &b);
    let summed = zip_with(&a, &b, |x, y| x + y);

    // traverse - Apply function to each, collect results
    let results = traverse(&items, |item| Zombie::new(*item * 2));

    println!("Res: {}", z.get());
}

Documentation

Naming & Renaming

To provide a more modern and intuitive API, we have renamed core internal components from their original Sanskrit names (used in the C++ implementation) to English descriptors:

New Name Original Name Meaning
Runtime Trailokya The global state manager
Timeline Akasha The context/tock tree
Zombie Zombie Values that "die" (evict) and "resurrect" (recompute)

References

License

MIT