db/radixtree/MIGRATION.md

7.1 KiB

Migration Guide: V to Rust RadixTree

This document provides guidance for migrating from the V implementation of RadixTree to the Rust implementation.

API Changes

The Rust implementation maintains API compatibility with the V implementation, but with some idiomatic Rust changes:

V API

// Create a new radix tree
mut rt := radixtree.new(path: '/tmp/radixtree_test', reset: true)!

// Set a key-value pair
rt.set('test', 'value1'.bytes())!

// Get a value by key
value := rt.get('test')!

// Update a value at a prefix
rt.update('prefix', 'new_value'.bytes())!

// Delete a key
rt.delete('test')!

// List keys with a prefix
keys := rt.list('prefix')!

// Get all values with a prefix
values := rt.getall('prefix')!

Rust API

// Create a new radix tree
let mut tree = RadixTree::new("/tmp/radixtree_test", true)?;

// Set a key-value pair
tree.set("test", b"value1".to_vec())?;

// Get a value by key
let value = tree.get("test")?;

// Update a value at a prefix
tree.update("prefix", b"new_value".to_vec())?;

// Delete a key
tree.delete("test")?;

// List keys with a prefix
let keys = tree.list("prefix")?;

// Get all values with a prefix
let values = tree.getall("prefix")?;

Key Differences

  1. Error Handling: The Rust implementation uses Rust's Result type for error handling, while the V implementation uses V's ! operator.

  2. String Handling: The Rust implementation uses Rust's &str for string parameters and String for string return values, while the V implementation uses V's string type.

  3. Binary Data: The Rust implementation uses Rust's Vec<u8> for binary data, while the V implementation uses V's []u8 type.

  4. Constructor: The Rust implementation uses a constructor function with separate parameters, while the V implementation uses a struct with named parameters.

  5. Ownership: The Rust implementation follows Rust's ownership model, requiring mutable references for methods that modify the tree.

Data Compatibility

The Rust implementation maintains data compatibility with the V implementation:

  • The same serialization format is used for nodes
  • The same OurDB storage format is used
  • Existing RadixTree data created with the V implementation can be read by the Rust implementation

Migration Steps

  1. Update Dependencies: Replace the V RadixTree dependency with the Rust RadixTree dependency in your project.

  2. Update Import Statements: Replace V import statements with Rust use statements.

    // V
    import freeflowuniverse.herolib.data.radixtree
    
    // Rust
    use radixtree::RadixTree;
    
  3. Update Constructor Calls: Replace V constructor calls with Rust constructor calls.

    // V
    mut rt := radixtree.new(path: '/path/to/db', reset: false)!
    
    // Rust
    let mut tree = RadixTree::new("/path/to/db", false)?;
    
  4. Update Method Calls: Replace V method calls with Rust method calls.

    // V
    rt.set('key', 'value'.bytes())!
    
    // Rust
    tree.set("key", b"value".to_vec())?;
    
  5. Update Error Handling: Replace V error handling with Rust error handling.

    // V
    if value := rt.get('key') {
        println('Found: ${value.bytestr()}')
    } else {
        println('Error: ${err}')
    }
    
    // Rust
    match tree.get("key") {
        Ok(value) => println!("Found: {}", String::from_utf8_lossy(&value)),
        Err(e) => println!("Error: {}", e),
    }
    
  6. Update String Conversions: Replace V string conversions with Rust string conversions.

    // V
    value.bytestr() // Convert []u8 to string
    
    // Rust
    String::from_utf8_lossy(&value) // Convert Vec<u8> to string
    

Example Migration

V Code

module main

import freeflowuniverse.herolib.data.radixtree

fn main() {
    mut rt := radixtree.new(path: '/tmp/radixtree_test', reset: true) or {
        println('Error creating RadixTree: ${err}')
        return
    }

    rt.set('hello', 'world'.bytes()) or {
        println('Error setting key: ${err}')
        return
    }

    rt.set('help', 'me'.bytes()) or {
        println('Error setting key: ${err}')
        return
    }

    if value := rt.get('hello') {
        println('hello: ${value.bytestr()}')
    } else {
        println('Error getting key: ${err}')
        return
    }

    keys := rt.list('hel') or {
        println('Error listing keys: ${err}')
        return
    }
    println('Keys with prefix "hel": ${keys}')

    values := rt.getall('hel') or {
        println('Error getting all values: ${err}')
        return
    }
    println('Values with prefix "hel":')
    for i, value in values {
        println('  ${i}: ${value.bytestr()}')
    }

    rt.delete('help') or {
        println('Error deleting key: ${err}')
        return
    }
    println('Deleted "help"')
}

Rust Code

use radixtree::RadixTree;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut tree = RadixTree::new("/tmp/radixtree_test", true)
        .map_err(|e| format!("Error creating RadixTree: {}", e))?;

    tree.set("hello", b"world".to_vec())
        .map_err(|e| format!("Error setting key: {}", e))?;

    tree.set("help", b"me".to_vec())
        .map_err(|e| format!("Error setting key: {}", e))?;

    let value = tree.get("hello")
        .map_err(|e| format!("Error getting key: {}", e))?;
    println!("hello: {}", String::from_utf8_lossy(&value));

    let keys = tree.list("hel")
        .map_err(|e| format!("Error listing keys: {}", e))?;
    println!("Keys with prefix \"hel\": {:?}", keys);

    let values = tree.getall("hel")
        .map_err(|e| format!("Error getting all values: {}", e))?;
    println!("Values with prefix \"hel\":");
    for (i, value) in values.iter().enumerate() {
        println!("  {}: {}", i, String::from_utf8_lossy(value));
    }

    tree.delete("help")
        .map_err(|e| format!("Error deleting key: {}", e))?;
    println!("Deleted \"help\"");

    Ok(())
}

Performance Considerations

The Rust implementation should provide similar or better performance compared to the V implementation. However, there are some considerations:

  1. Memory Usage: The Rust implementation may have different memory usage patterns due to Rust's ownership model.

  2. Error Handling: The Rust implementation uses Rust's Result type, which may have different performance characteristics compared to V's error handling.

  3. String Handling: The Rust implementation uses Rust's string types, which may have different performance characteristics compared to V's string types.

Troubleshooting

If you encounter issues during migration, check the following:

  1. Data Compatibility: Ensure that the data format is compatible between the V and Rust implementations.

  2. API Usage: Ensure that you're using the correct API for the Rust implementation.

  3. Error Handling: Ensure that you're handling errors correctly in the Rust implementation.

  4. String Encoding: Ensure that string encoding is consistent between the V and Rust implementations.

If you encounter any issues that are not covered in this guide, please report them to the project maintainers.