Python to Rust Testing with Depyler

Chapter 12: Python to Rust Testing with Depyler

You've built comprehensive Python test suites. But what if you could leverage Rust's safety guarantees and performance? This chapter introduces Depyler, a Python-to-Rust transpiler that preserves program semantics while providing compile-time safety. You'll learn how to migrate Python code to Rust and verify correctness through testing.

What is Depyler?

Depyler transpiles annotated Python code into idiomatic Rust, preserving semantics while providing memory safety and performance improvements.

Key Features:

  • Type-directed transpilation: Uses Python type annotations to generate Rust types
  • Memory safety analysis: Infers ownership and borrowing patterns
  • Semantic verification: Property-based testing verifies behavioral equivalence
  • Test preservation: Transpiles both code AND tests

Why Transpile Python to Rust?

  • Performance: 10-100x faster execution for compute-intensive code
  • Safety: Compile-time memory safety guarantees
  • Concurrency: Fearless concurrency with Rust's ownership system
  • Deployment: Single binary, no interpreter needed

Install Depyler:

cargo install depyler

Basic Transpilation Workflow

Input (fibonacci.py):

Transpile:

depyler transpile fibonacci.py

Output (fibonacci.rs):

fn fibonacci(n: i32) -> i32 {
    if n <= 1 {
        return n;
    }
    fibonacci(n - 1) + fibonacci(n - 2)
}

Notice: Type annotations (int) become Rust types (i32), syntax adapts automatically.

Testing Transpiled Code

Depyler doesn't just transpile code—it verifies correctness through semantic equivalence testing.

Python Test (test_fibonacci.py):

import pytest

def test_fibonacci_base_cases():
    assert fibonacci(0) == 0
    assert fibonacci(1) == 1

def test_fibonacci_recursive():
    assert fibonacci(5) == 5
    assert fibonacci(10) == 55

Transpile with Verification:

depyler transpile fibonacci.py --verify

Depyler generates property-based tests to verify Python and Rust implementations produce identical results:

Running semantic verification...
Testing fibonacci(0)... ✅ Match
Testing fibonacci(1)... ✅ Match
Testing fibonacci(5)... ✅ Match
Testing fibonacci(10)... ✅ Match
Testing fibonacci(-1)... ✅ Match

Semantic Verification: PASSED
100% behavioral equivalence confirmed

If results differ, transpilation fails. This catches subtle semantic bugs.

Type System Mapping

Depyler maps Python types to Rust types conservatively:

Primitive Types:

  • inti32 (or i64 for large values)
  • floatf64
  • boolbool
  • strString

Collections:

  • list[T]Vec<T> (owned) or &[T] (borrowed)
  • dict[K, V]HashMap<K, V>
  • set[T]HashSet<T>
  • tuple[T, U](T, U)

Option and Result:

  • T | NoneOption<T>
  • Functions that raise exceptions → Result<T, E>

Example:

def find_user(user_id: int) -> dict[str, str] | None:
    if user_id < 0:
        raise ValueError("Invalid user ID")
    if user_id == 0:
        return None
    return {"id": str(user_id), "name": "User"}

Transpiles to:

fn find_user(user_id: i32) -> Result<Option<HashMap<String, String>>, String> {
    if user_id < 0 {
        return Err("Invalid user ID".to_string());
    }
    if user_id == 0 {
        return Ok(None);
    }
    let mut user = HashMap::new();
    user.insert("id".to_string(), user_id.to_string());
    user.insert("name".to_string(), "User".to_string());
    Ok(Some(user))
}

Exception handling becomes Result<T, E>, None checks become Option<T>.

Migration Complexity Analysis

Before transpiling a large codebase, analyze migration effort:

depyler analyze myproject.py

Output example:

Migration Complexity Analysis
==============================
File: myproject.py
Lines of Code: 450
Functions: 23

Complexity Score: MEDIUM (65/100)

Supported Features:
  ✅ Type annotations: 100%
  ✅ Basic types: 100%
  ✅ Collections: 95%
  ✅ Control flow: 100%
  ✅ Exception handling: 90%

Unsupported Features:
  ❌ Dynamic evaluation (eval): 2 instances
  ❌ Reflection (getattr): 1 instance
  ⚠️  Multiple inheritance: 1 class

Estimated Effort: 2-3 days
Recommendation: Refactor 3 areas before transpiling

This helps prioritize refactoring before migration.

Transpiling Tests

Depyler transpiles pytest tests to Rust tests:

Python (test_math.py):

import pytest

def test_addition():
    assert add(2, 3) == 5

def test_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

Transpiled Rust (tests/math_test.rs):

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_addition() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    #[should_panic(expected = "ZeroDivisionError")]
    fn test_division_by_zero() {
        divide(10, 0).unwrap();
    }
}

Run Rust tests:

cargo test

All Python test semantics preserved in Rust.

Ownership and Borrowing Inference

Depyler infers ownership patterns from usage:

Example 1: Ownership Transfer

def process(data: list[int]) -> list[int]:
    data.append(42)
    return data

Rust:

fn process(mut data: Vec<i32>) -> Vec<i32> {
    data.push(42);
    data
}

data is moved (ownership transferred).

Example 2: Borrowing

def sum_list(data: list[int]) -> int:
    return sum(data)

Rust:

fn sum_list(data: &[i32]) -> i32 {
    data.iter().sum()
}

data is borrowed (no ownership transfer).

Depyler analyzes usage to choose appropriate ownership semantics.

MCP Integration

Depyler provides MCP tools for AI-powered transpilation workflows:

Setup (~/.config/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "depyler": {
      "command": "depyler",
      "args": ["agent", "start", "--foreground"]
    }
  }
}

Available Tools:

  • transpile_python - Convert Python to Rust
  • analyze_migration_complexity - Estimate effort
  • verify_transpilation - Check semantic equivalence
  • pmat_quality_check - Quality analysis

AI assistants can now help with Python-to-Rust migrations.

Incremental Migration Strategy

Don't transpile everything at once. Use incremental migration:

Step 1: Identify Performance Bottlenecks

python -m cProfile myapp.py

Find hot paths (functions consuming most time).

Step 2: Transpile Hot Paths

depyler transpile hot_functions.py --verify

Step 3: Build Rust Library

cargo build --release

Step 4: Call from Python via PyO3

import mylib_rust

result = mylib_rust.hot_function(data)

Step 5: Verify Performance Gains
Benchmark before/after. Expect 10-100x speedup for compute-heavy code.

Step 6: Gradually Migrate More
Repeat for other modules.

Limitations and Workarounds

Not Supported:

  • eval() / exec() - No dynamic execution in Rust
  • Monkey patching - Rust doesn't allow runtime modification
  • Multiple inheritance - Rust uses traits instead
  • Runtime reflection - Limited in Rust

Workarounds:

  • Refactor eval() to static code
  • Replace monkey patching with explicit functions
  • Use traits for multiple inheritance patterns
  • Use compile-time reflection (macros)

Depyler warns about unsupported features during analysis.

Testing Strategy for Transpiled Code

  1. Semantic Verification: Run --verify to ensure behavioral equivalence
  2. Property-Based Tests: Use Hypothesis in Python, proptest in Rust
  3. Benchmark Tests: Verify performance improvements
  4. Integration Tests: Test Rust/Python interop
  5. Fuzzing: Use cargo-fuzz to find edge cases

Combine Python testing knowledge with Rust safety guarantees.

Real-World Use Case

Scenario: Data processing pipeline runs slowly in Python.

Original (process.py):

def analyze_data(data: list[dict]) -> dict:
    """Analyze large dataset."""
    total = sum(item["value"] for item in data)
    avg = total / len(data) if data else 0
    return {"total": total, "average": avg}

Performance: 2.5 seconds for 1M records.

Transpile:

depyler transpile process.py --verify
cargo build --release

Performance: 25ms for 1M records. 100x faster.

Tests still pass. Semantics preserved. Zero bugs introduced.

Advanced: Custom Type Mappings

Configure custom type mappings in depyler.toml:

[type_mapping]
"decimal.Decimal" = "rust_decimal::Decimal"
"datetime.datetime" = "chrono::DateTime<Utc>"
"pathlib.Path" = "std::path::PathBuf"

This handles third-party types not in standard library.

Combining PMAT and Depyler

Use both tools together for maximum quality assurance during migration:

Step 1: PMAT Quality Check (Python)

# Ensure Python tests are comprehensive
pmat mutate test calculator.py

# Output
Mutation Score: 85%
✅ High-quality tests confirmed

Step 2: Depyler Transpilation

# Transpile with semantic verification
depyler transpile calculator.py --verify

# Output
✅ Transpilation successful
✅ Semantic verification passed

Step 3: PMAT Quality Check (Rust)

# Run mutation testing on Rust code
pmat mutate test calculator.rs --language rust

# Output
Mutation Score: 87%
✅ Quality maintained after migration

Step 4: Compare Scores

  • Python: 85% mutation score
  • Rust: 87% mutation score
  • Difference: +2% (acceptable)

If Rust score drops significantly, investigate test coverage gaps introduced during transpilation.

Step 5: Performance Benchmark

# Python
time python benchmark.py
# 2.5 seconds

# Rust
time cargo run --release
# 25ms (100x faster)

This workflow ensures quality doesn't degrade during migration while delivering massive performance gains.

Common Issues and Solutions:

Issue: Rust mutation score lower than Python

  • Cause: Some Python tests didn't transpile correctly
  • Solution: Manually review transpiled tests, add missing assertions

Issue: Semantic verification fails

  • Cause: Floating-point precision differences
  • Solution: Use approximate equality (pytest.approxassert_approx)

Issue: Performance not as expected

  • Cause: Debug build instead of release
  • Solution: Always benchmark with cargo build --release

Best Practices for Python-Rust Migration

1. Start Small
Don't transpile your entire application. Start with:

  • One hot function (identified via profiling)
  • Pure functions without side effects
  • Functions with clear type annotations
  • Code without dynamic features

2. Add Type Annotations First
Before transpiling, ensure comprehensive type coverage:

# Before (untyped)
def process(data):
    return [x * 2 for x in data]

# After (typed)
def process(data: list[int]) -> list[int]:
    return [x * 2 for x in data]

Depyler requires type annotations for accurate transpilation.

3. Test Thoroughly Before Transpiling
Achieve 80%+ code coverage and 75%+ mutation score in Python first. If Python tests are weak, Rust tests will be weak too.

4. Use Semantic Verification Always
Never skip --verify. It's your safety net:

# Always use --verify
depyler transpile module.py --verify

# Don't do this
depyler transpile module.py  # ❌ No verification

5. Profile Before and After
Measure actual performance gains:

import time

start = time.time()
result = python_function(data)
print(f"Python: {time.time() - start:.3f}s")

start = time.time()
result = rust_function(data)
print(f"Rust: {time.time() - start:.3f}s")

Don't assume—measure. Some functions won't benefit from Rust.

6. Keep Python Version as Fallback
During migration, maintain both versions:

try:
    from mylib_rust import compute
except ImportError:
    from mylib_python import compute

This allows graceful fallback if Rust version has issues.

Course Recommendations

Python to Rust Migration

  • Complete transpilation workflows
  • Performance optimization techniques
  • PyO3 integration patterns
  • Enroll at paiml.com

Rust for Python Developers

  • Ownership and borrowing explained
  • Type system deep dive
  • Building fast Python extensions
  • Enroll at paiml.com

Advanced Testing with Property-Based Testing

  • Hypothesis and proptest mastery
  • Semantic equivalence testing
  • Cross-language test strategies
  • Enroll at paiml.com

Quiz

Final Thoughts

Depyler bridges Python's expressiveness with Rust's safety and performance. By preserving semantics through verification, it enables confident migration of critical code paths. Combined with comprehensive testing (pytest + PMAT + property-based testing), Depyler makes Python-to-Rust migration practical and safe. Use it to optimize hotspots while maintaining the testing rigor you've built throughout this book.

Congratulations on completing Testing in Python! You now have a comprehensive toolkit: unit testing, TDD, mocking, coverage, mutation testing, property-based testing, PMAT quality gates, and Python-to-Rust migration with Depyler. Apply these techniques to build reliable, maintainable, and performant Python applications.

📝 Test Your Knowledge: Python to Rust Testing with Depyler

Take this quiz to reinforce what you've learned in this chapter.