Detail of chromed gear with text nodyn!! inside

nodyn Easy polymorphism with enums

Polymorphic gear

nodyn provides a Rust macro for creating wrapper enums that encapsulate a fixed set of types with automatic implementations for From, TryFrom, and delegated methods or traits. This is ideal for scenarios where you need to store values of different types in a type-safe, zero-cost way, as an alternative to trait objects.

“This is a perfectly good solution when our interchangeable items are a fixed set of types that we know when our code is compiled.”
The Rust Programming Language

Quick Start

Create a simple enum wrapper for i32, String, and f64:

nodyn::nodyn! {
    #[derive(Debug, PartialEq)]
    pub enum Value {
        i32,
        String,
        f64,
    }
}

let values: Vec<Value> = vec![
    42.into(),                  // Converts i32 to Value::I32
    "hello".to_string().into(), // Converts String to Value::String
    3.14.into(),                // Converts f64 to Value::F64
];

for value in values {
    match value {
        Value::I32(n) => println!("Integer: {}", n),
        Value::String(s) => println!("String: {}", s),
        Value::F64(f) => println!("Float: {}", f),
    }
}

Features

Use impl directives to enable features explicitly (e.g., impl TryInto is_as). Cargo features (try_into, is_as, introspection) are deprecated but supported for backward compatibility. See the Feature Flags section for details.

Vec Wrapper Example

Use the vec feature to create a polymorphic Vec with variant-specific methods:

nodyn::nodyn! {
    #[derive(Debug, Clone)]
    pub enum Item {
        i32,    // Gold coins
        String, // Weapon names
        f64,    // Health potions (liters)
    }
    vec Inventory;
}

let mut inventory = inventory![100, "sword".to_string(), 0.5, "axe".to_string()];
// Add more gold
inventory.push(50);
// Check for weapons in the inventory
assert!(inventory.any_string());
// Total gold coins
let total_gold = inventory.iter_i32().sum::<i32>();
assert_eq!(total_gold, 150);
// Get a potion
if let Some(potion) = inventory.first_f64() {
    println!("Found potion: {} liters", potion); // Prints: 0.5 liters
}

See the Polymorphic Vec section in the Documentation

Method Delegation Example

nodyn::nodyn! {
    enum Container { String, Vec<u8> }
    impl {
        fn len(&self) -> usize;
        fn is_empty(&self) -> bool;
        fn clear(&mut self);
    }
}

let mut container: Container = "hello".to_string().into();
assert_eq!(container.len(), 5);
assert!(!container.is_empty());
container.clear();
assert!(container.is_empty());

Trait Implementation Example

Delegate entire traits when all wrapped types implement them:

use std::fmt::{self, Display};

// All wrapped types implement Display
nodyn::nodyn! {
    enum Displayable { i32, String, f64 }

    impl Display {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
    }

    vec Displayables;
}

let values = displayables![42, "hello".to_string(), 3.14];

for val in values {
    println!("{}", val); // Uses delegated Display implementation
}

JSON Example

This example creates a JSON-like data structure with nested arrays, showcasing trait delegation and Polymorphic Vec features:

use std::fmt;

#[derive(Debug, Clone)]
pub struct Null;

impl fmt::Display for Null {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "null")
    }
}

#[derive(Debug, Clone)]
pub struct JsonArray(JsonValueVec);

impl fmt::Display for JsonArray {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = self.0.iter().map(ToString::to_string).collect::<Vec<_>>().join(", ");
        write!(f, "[{s}]")
    }
}

nodyn::nodyn! {
    #[derive(Debug, Clone)]
    pub enum JsonValue {
        Null,
        bool,
        f64,
        String,
        JsonArray,
    }
    
    vec;

    impl fmt::Display {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
    }

    impl {
        pub const fn json_type_name(&self) -> &'static str {
            match self {
                Self::Null(_) => "null",
                Self::Bool(_) => "boolean",
                Self::F64(_) => "number",
                Self::String(_) => "string",
                Self::JsonArray(_) => "array",
            }
        }
    }
}


let mut values = JsonValueVec::default();
values.push(Null);
values.push(true);
values.push(42.0);
values.push("hello".to_string());
values.push(JsonArray(json_value_vec![Null, false, 33.0]));

for val in &values {
    println!("{}: {}", val.json_type_name(), val);
}

Installation

Add nodyn to your Cargo.toml:

[dependencies]
nodyn = "0.2.0"

Comparison

Featurenodynenum_dispatchsum_typeBox
Runtime CostZeroZeroZeroHeap allocation
Trait Delegation✅ Yes✅ Scoped only❌ No✅ Yes
Method Delegation✅ Yes❌ No❌ No❌ No
Type Introspection✅ Built-in❌ No❌ No❌ No
Vec Wrapper✅ Yes❌ No❌ No❌ No
Compile-Time KnownRequiredRequiredRequiredNot required
Memory OverheadDiscriminant onlyDiscriminant onlyDiscriminant onlyPointer + vtable

Documentation

Contributing

Contributions are welcome! Check out the GitHub repository for issues, feature requests, or to submit pull requests.

License

This project is licensed under the MIT License.