concrete-type 0.2.0

A procedural macro crate for mapping enum variants to concrete types, enabling type-level programming based on runtime values
Documentation

concrete-type

Crates.io Documentation MIT License

A Rust procedural macro library for mapping enum variants to concrete types, enabling type-level programming based on runtime enum values.

Table of Contents

Overview

concrete-type provides procedural macros that create a relationship between enum variants and specific concrete types. This enables:

  • Type-level programming with enums
  • Executing code with concrete type knowledge at compile time based on runtime enum values
  • Generating helpful utility methods and macros for working with the concrete types
  • Optionally carrying configuration data with enum variants
  • Composing multiple enum types together through nested macro calls

Installation

Add this to your Cargo.toml:

[dependencies]
concrete-type = "0.1.0"

Features

#[derive(Concrete)]

  • Map enum variants to concrete types with #[concrete = "path::to::Type"] attribute
  • Generated methods:
    • concrete_type_id(): Returns the TypeId of the concrete type for a variant
    • concrete_type_name(): Returns the name of the concrete type as a string
    • with_concrete_type(): Executes a function with knowledge of the concrete type
  • Auto-generated macros for type-level dispatch using the snake_case name of the enum

#[derive(ConcreteConfig)]

  • Map enum variants with configuration data to concrete types
  • Each variant must have a single tuple field containing the configuration
  • Generated methods:
    • concrete_type_id(): Returns the TypeId of the concrete type for a variant
    • concrete_type_name(): Returns the name of the concrete type as a string
    • config(): Returns a reference to the configuration data
  • Auto-generated macros for type-level dispatch with access to both the concrete type and config data

Examples

Basic Usage

use concrete_type::Concrete;

#[derive(Concrete, Clone, Copy)]
enum Exchange {
    #[concrete = "exchanges::Binance"]
    Binance,
}

mod exchanges {
    use crate::ExchangeApi;

    pub struct Binance;

    impl Binance {
        fn new() -> Self {
            Binance
        }

        fn name(&self) -> &'static str {
            "SomeBinanceName"
        }
    }
}

// Use the auto-generated 'exchange!' macro to work with concrete types
let exchange = Exchange::Binance;
let name = exchange!(exchange; ExchangeImpl => {
    // Here, ExchangeImpl is aliased to the concrete type (exchanges::Binance)
    let instance = ExchangeImpl::new();
    instance.name()
});

Enums with Config Data

use concrete_type::ConcreteConfig;

// Define concrete types and configuration types
mod exchanges {
    pub trait ExchangeApi {
        type Config;
        fn new(config: Self::Config) -> Self;
        fn name(&self) -> &'static str;
    }

    pub struct Binance;
    pub struct BinanceConfig;

    impl ExchangeApi for Binance {
        type Config = BinanceConfig;
        fn new(_: Self::Config) -> Self { Self }
        fn name(&self) -> &'static str { "binance" }
    }

    pub struct Okx;
    pub struct OkxConfig;

    impl ExchangeApi for Okx {
        type Config = OkxConfig;
        fn new(_: Self::Config) -> Self { Self }
        fn name(&self) -> &'static str { "okx" }
    }
}

// Define the exchange config enum with concrete type mappings and config data
#[derive(ConcreteConfig)]
enum ExchangeConfig {
    #[concrete = "exchanges::Binance"]
    Binance(exchanges::BinanceConfig),
    #[concrete = "exchanges::Okx"]
    Okx(exchanges::OkxConfig),
}

// Import the trait for access to its methods
use exchanges::ExchangeApi;

// Using the auto-generated exchange_config! macro:
let config = ExchangeConfig::Binance(exchanges::BinanceConfig);
let name = exchange_config!(config; (Exchange, config_param) => {
    // Inside this block:
    // - Exchange is aliased to exchanges::Binance
    // - config_param is the BinanceConfig instance
    Exchange::new(config_param).name()
});

Composing Multiple Enum Types

For more complex use cases, you can compose multiple enums together through nested macros:

use concrete_type::Concrete;
use std::marker::PhantomData;

// Define enums that map to concrete types
#[derive(Concrete, Clone, Copy)]
enum Exchange {
    #[concrete = "exchanges::Binance"]
    Binance,
    #[concrete = "exchanges::Okx"]
    Okx,
}

#[derive(Concrete)]
enum Strategy {
    #[concrete = "strategies::StrategyA"]
    StrategyA,
    #[concrete = "strategies::StrategyB"]
    StrategyB,
}

// A struct with type parameters
struct TradingSystem<Exchange, Strategy> {
    phantom: PhantomData<(Exchange, Strategy)>,
}

// Implement for concrete type combinations
impl TradingSystem<exchanges::Binance, strategies::StrategyA> {
    pub fn new() -> Self {
        Self { phantom: PhantomData }
    }

    pub fn name(&self) -> &'static str { 
        "binance_strategy_a" 
    }
}

// Using multiple enums together with nested macros
let exchange = Exchange::Binance;
let strategy = Strategy::StrategyA;

let name = exchange!(exchange; Exchange => {
    strategy!(strategy; Strategy => {
        TradingSystem::<Exchange, Strategy>::new().name()
    })
});
assert_eq!(name, "binance_strategy_a");

License

MIT