arc-handle 1.2.0

Proc macro for generating Arc-based handle wrappers for traits
Documentation

arc-handle

A Rust procedural macro crate for generating Arc-based handle wrappers for traits, enabling easy trait object sharing across threads.

Overview

The #[arc_handle] attribute macro transforms a trait definition into a thread-safe handle struct that wraps trait implementations in Arc<dyn Trait + Send + Sync>. This pattern is useful when you need to share trait objects across multiple threads or async tasks.

Features

  • Thread-safe sharing: Generated handles implement Clone, Send, and Sync
  • Async/await support: Properly handles both sync and async trait methods
  • Zero-cost abstraction: Minimal overhead over direct Arc usage
  • Easy integration: Simple attribute macro application

Installation

Add this to your Cargo.toml:

[dependencies]
arc-handle = "1.0.0"

Usage

Basic Example

use arc_handle::arc_handle;

#[arc_handle]
pub trait Calculator {
    fn add(&self, a: i32, b: i32) -> i32;
    fn multiply(&self, a: i32, b: i32) -> i32;
}

// Implementation
struct BasicCalculator;

impl CalculatorImpl for BasicCalculator {
    fn add(&self, a: i32, b: i32) -> i32 {
        a + b
    }
    
    fn multiply(&self, a: i32, b: i32) -> i32 {
        a * b
    }
}

// Usage
fn main() {
    let calc = Calculator::new(BasicCalculator);
    
    // Can be cloned and shared across threads
    let calc_clone = calc.clone();
    
    println!("2 + 3 = {}", calc.add(2, 3));
    println!("2 * 3 = {}", calc_clone.multiply(2, 3));
}

Async Example

use arc_handle::arc_handle;
use async_trait::async_trait;

#[arc_handle]
#[async_trait]
pub trait DataProcessor {
    async fn process_data(&self, data: Vec<u8>) -> Result<String, Box<dyn std::error::Error>>;
    fn get_name(&self) -> &str;
}

struct JsonProcessor {
    name: String,
}

#[async_trait]
impl DataProcessorImpl for JsonProcessor {
    async fn process_data(&self, data: Vec<u8>) -> Result<String, Box<dyn std::error::Error>> {
        // Process data asynchronously
        tokio::time::sleep(std::time::Duration::from_millis(10)).await;
        Ok(String::from_utf8(data)?)
    }
    
    fn get_name(&self) -> &str {
        &self.name
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let processor = DataProcessor::new(JsonProcessor {
        name: "JSON Processor".to_string(),
    });
    
    let data = b"Hello, World!".to_vec();
    let result = processor.process_data(data).await?;
    
    println!("Processed: {}", result);
    println!("Processor: {}", processor.get_name());
    
    Ok(())
}

Default Method Implementations

Trait methods with default bodies are supported. The handle delegates to the trait object, so implementors may override the default and the handle will dispatch to their override:

use arc_handle::arc_handle;

#[arc_handle]
pub trait Worker {
    fn name(&self) -> &str;

    fn greet(&self) -> String {
        format!("Hello from {}", self.name())
    }
}

struct Robot;

impl WorkerImpl for Robot {
    fn name(&self) -> &str {
        "Rob"
    }
}

fn main() {
    let worker = Worker::new(Robot);
    // Uses the default body, which calls `name()` on the implementor.
    assert_eq!(worker.greet(), "Hello from Rob");
}

How It Works

When you apply #[arc_handle] to a trait:

  1. Trait Transformation: The original trait is renamed to TraitImpl
  2. Handle Generation: A new struct with the original trait name is created
  3. Method Delegation: All trait methods are implemented on the handle, delegating to the inner Arc<dyn TraitImpl + Send + Sync>
  4. Constructor Methods: new() and from_boxed() methods are generated for easy instantiation

Generated Code Structure

For a trait named MyTrait, the macro generates:

// Original trait renamed
pub trait MyTraitImpl {
    // ... original methods
}

// Generated handle struct
#[derive(Clone)]
pub struct MyTrait {
    inner: std::sync::Arc<dyn MyTraitImpl + Send + Sync>,
}

impl MyTrait {
    pub fn new(inner: impl MyTraitImpl + Send + Sync + 'static) -> Self { /* ... */ }
    pub fn from_boxed(inner: Box<dyn MyTraitImpl + Send + Sync>) -> Self { /* ... */ }
    
    // Delegating methods for each trait method
    // ...
}

Requirements

  • Rust 2024 edition or later
  • For async traits, use with #[async_trait] from the async-trait crate

License

Licensed under either of

at your option.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.