arc-handle

Crates.ioarc-handle
lib.rsarc-handle
version1.0.0
created_at2025-08-08 02:49:35.130281+00
updated_at2025-08-08 02:49:35.130281+00
descriptionProc macro for generating Arc-based handle wrappers for traits
homepage
repositoryhttps://gitlab.com/akarifur/arc-handle
max_upload_size
id1786127
size25,470
Jarrett Tierney (serates)

documentation

README

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(())
}

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.

Commit count: 0

cargo fmt