nodyn Easy polymorphism with enums
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
- Automatic Variant Creation: Generates enum variants for types
like
i32
,String
, or custom structs with CamelCase naming. - Type Conversions: Implements
From<T>
for each variant andTryFrom<Enum>
with theimpl TryInto
directive. - Method and Trait Delegation: Delegates methods or entire traits to underlying types.
- Type Introspection: Provides
count
,types
, andtype_name
methods withimpl introspection
. - Polymorphic Vec: Generates a
Vec<Enum>
wrapper with avec!
-like macro and variant-specific methods (e.g.,first_i32
,count_string
) viavec
. - Customizable Variants: Allows overriding default variant names.
- Supported Types: Handles path types, references, arrays, and tuples.
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
Feature | nodyn | enum_dispatch | sum_type | Box |
---|---|---|---|---|
Runtime Cost | Zero | Zero | Zero | Heap 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 Known | Required | Required | Required | Not required |
Memory Overhead | Discriminant only | Discriminant only | Discriminant only | Pointer + vtable |
- enum_dispatch: Optimizes dynamic dispatch with zero-cost enums but lacks method delegation and Vec wrappers.
- sum_type: Simplifies enum creation but lacks advanced features like delegation or introspection.
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.