Crate nodyn

Crate nodyn 

Source
Expand description

Easy polymorphism with enums

The nodyn! macro generates a Rust enum that wraps a fixed set of types, providing automatic implementations for type conversions, method delegation, and trait delegation. This enables type-safe storage of different types without the runtime overhead of trait objects, ideal for scenarios requiring zero-cost abstractions for a known set of types at compile time.

§Why use Nodyn?

In Rust, handling values of different types typically involves:

  • Trait Objects: Enable dynamic dispatch but incur runtime overhead and type erasure.
  • Enum Wrappers: Offer type safety and zero-cost abstractions for a fixed set of types, as described in The Rust Programming Language.

The nodyn! macro simplifies creating enum wrappers by generating boilerplate for variant creation, type conversions, method/trait delegation, and introspection utilities. nodyn! can also generate a special polymorphic Vec for your enum.

§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),
    }
}

§Key Features

  • Automatic Variant Creation: Generates an enum with variants for specified types.
  • Type Conversion: Implements From<T> for each variant type and optionally TryFrom<Enum> for T for non-reference types (with TryInto feature). Additional type conversion tools can be generated with the as_is feature
  • Method and Trait Delegation: Delegates methods or entire traits to underlying types.
  • Type Introspection: Provides count, types, and type_name methods to query variant information (with introspection feature).
  • Custom Variant Names: Allows overriding default variant names for clarity.
  • Polymorphic Vecs: Generates a Vec<Enum> wrapper with delegated Vec methods and and extra functionality such as construction macro to leverage the enums features.

§Trait Delegation Example

Here’s an example inspired by Listing 10-13 from The Rust Programming Language, demonstrating trait delegation:

pub trait Summary {
    fn summarize(&self) -> String;
}

#[derive(Debug)]
pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

#[derive(Debug)]
pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

nodyn::nodyn! {
    #[derive(Debug)]
    pub enum Article {
        NewsArticle,
        SocialPost,
    }

    impl Summary {
        fn summarize(&self) -> String;
    }
}

let article = Article::from(NewsArticle {
    headline: String::from("Rust 2.0 Released"),
    location: String::from("Internet"),
    author: String::from("Rust Team"),
    content: String::from("..."),
});

assert_eq!(
    article.summarize(),
    "Rust 2.0 Released, by Rust Team (Internet)"
);

§Supported Types

The macro supports these type categories with automatic variant name generation:

Type CategoryExampleGenerated VariantNotes
Path typesString, i32, Vec<T>String, I32, VecTCamelCase conversion
References&strStrRefAdds Ref suffix
Arrays[i32; 4]I32Array4Adds Array{len} suffix
Tuples(i32, String)I32StringConcatenates types

§Complex Types Example

nodyn::nodyn! {
    #[derive(Debug)]
    pub enum ComplexEnum<'a> {
        i32,                    // I32
        String,                 // String
        (u8, u16),              // U8U16
        [bool; 2],              // BoolArray2
        &'a str,                // StrRef
        Vec<String>,            // VecString
    }
    vec;
}

let values = complex_enum_vec![
    42i32,
    "hello",
    (1u8, 2u16),
    [true, false],
    vec!["a".to_string()],
];

§Syntax

nodyn::nodyn! {
    [ #[attribute] ]
    [ #[module_path = "full::module::path"]]
    [pub] enum EnumName [<'lifetime>] {
        [VariantName(Type),]
        [Type,]
    }

    [impl TryInto | is_as | introspection]

    [impl TraitName {
        fn method_name(&self, args) -> ReturnType;
    }]

    [impl {
        fn method_name(&self, args) -> ReturnType;
    }]

    [vec [VecName]]

    [ #[vec] | #[vec(field)]
      [ #[attribute] ]
      [pub] struct CustomValues {
          [field: Type,]
      }
    ]
}

§Type Conversions and Introspection

§From and TryFrom

The macro automatically implements From<T> for each variant type. With the TryInto feature, it also implements TryFrom<Enum> for non-reference types.

nodyn::nodyn! {
    enum Value { i32, String }
    impl TryInto;
}

let val: Value = 42.into();
let num: i32 = i32::try_from(val).unwrap();
assert_eq!(num, 42);

§#[into(T)] Attribute

#[into(T)] Attribute: Allows a variant to be converted into another type T if a From implementation and variant exists.

nodyn::nodyn! {
    pub enum Foo {
        i64,
        #[into(i64)]
        i32,
    }
    impl TryInto;
}

let foo: Foo = 42.into();
assert_eq!(i64::try_from(foo), Ok(42i64));

§Introspection Methods (with introspection)

Enable type introspection with the introspection feature to query variant information:

nodyn::nodyn! {
    enum Value { i32, String, f64 }
    impl introspection;
}

assert_eq!(Value::count(), 3);
assert_eq!(Value::types(), ["i32", "String", "f64"]);
let val: Value = 42.into();
assert_eq!(val.type_name(), "i32");

§Type Checking and Conversion Methods (with is_as)

The is_as feature generates methods like is_* and try_as_* for variant-specific checks and conversions:

nodyn::nodyn! {
    enum Container { String, Vec<u8> }
    impl is_as;
}

let container: Container = "hello".to_string().into();
assert!(container.is_string());
assert!(!container.is_vec_u8());
if let Some(s) = container.try_as_string() {
    println!("Got string: {}", s);
}
let container: Container = "hello".to_string().into();
if let Some(s_ref) = container.try_as_string_ref() {
    println!("String reference: {}", s_ref);
}

Note: *_ref() and *_mut() methods are not generated for variants that wrap references.

§Method and Trait Delegation

§Method Delegation

Delegate methods that exist on all wrapped types with the same signature:

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 Delegation

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
}

See the JSON Example for a practical application of trait delegation.

§Polymorphic Vec

The vec feature generates a Vec<Enum> wrapper with delegated Vec methods and variant-specific utilities. It supports flexible insertion via Into<Enum> and provides methods like first_*, count_*, and all_* for variant-specific access.

A vec!-like macro is also generated for easy initialization.

§Basic Polymorphic Vec

Using vec generates a wrapper named after the enum with the suffix Vec. The generated wrapper has the same derive attributes as the enum, plus Default. If the enum is Copy, that won’t be included in the derive for the wrapper. The visibility of the wrapper and its methods matches the enum’s.

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

let mut values = ValueVec::default();
values.push(42);                    // Accepts i32
values.push("hello".to_string());   // Accepts String
values.push(3.14);                  // Accepts f64
assert_eq!(values.first_i32(), Some(&42));
assert_eq!(values.len(), 3);
assert_eq!(values.count_string(), 1);

You can specify a custom name for the wrapper:

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

    /// A polymorphic vec wrapper around `Vec<Value>`.
    vec Values;
}

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

§Polymorphic Vec Features

§Filtering and Iteration

nodyn::nodyn! {
    #[derive(Debug, Clone, PartialEq)]
    pub enum Data<'a> {
        i32,
        &'a str,
        bool,
    }
    vec;
}

let mut data = data_vec![42, "hello", true, 99, "world", false];

for number in data.iter_i32() {
    println!("Number: {}", number);  // Prints: 42, 99
}

assert_eq!(data.count_str_ref(), 2);
assert_eq!(data.count_bool(), 2);

assert!(!data.all_i32());  // Not all items are i32
assert!(data.any_str_ref()); // At least one string exists

§Construction from Slices

nodyn::nodyn! {
    #[derive(Debug, Clone)]
    pub enum Number {
        i32,
        f64,
    }
    vec Numbers;
}

// Construct from homogeneous slices
let integers: &[i32] = &[1, 2, 3, 4, 5];
let mut numbers = Numbers::from(integers);

let floats = vec![1.1, 2.2, 3.3];
numbers.extend(floats);  // Extends with Vec<f64> via Into

assert_eq!(numbers.count_i32(), 5);
assert_eq!(numbers.count_f64(), 3);

§A vec!-like Macro

Nodyn generates a macro for your polymorphic vec with the name of the wrapper changed to snake case. As the nodyn! macro does not know where it is invoked, you have to tell it the full module path including the crate name, so the generated macro works correctly. If you don’t specify the module path, it is assumed the polymorphic vec and enum are in the local scope.

The macro works like vec! but accepts any value within your enum and uses Into for automatic conversion.

The macro requires that the wrapper has #[derive(Default)] (the standard polymorphic vec always has this).

§Example

// in my_crate/src/foo/bar.rs:
nodyn::nodyn! {
    #[module_path = "my_crate::foo::bar"]
    #[derive(Debug, Clone)]
    pub enum Value<'a> {
        i32,
        &'a str,
        f64,
    }

    vec Values;
}

// elsewhere after importing values, etc:
let my_values = values!["hello", 42, "world", 0.1];

§Custom Polymorphic Vecs

Define a custom wrapper struct with additional fields using the #[vec] or #[vec(field_name)] attribute. Without a field name, ‘inner’ is used. nodyn! adds the field. Unlike the standard polymorphic vec, derive arguments are not copied from the enum. nodyn! does not implement Deref or DerefMut for custom wrappers, so you can! (but you have to call as_slice yourself).

I recommend you put #[derive(Default)] on your custom polymorphic vec so nodyn can generate the macro and implement the From trait.

§Example

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

    #[vec(inner_vec)]
    #[derive(Debug, Clone, Default)]
    pub struct CustomValues {
        metadata: String,
    }
}

let mut values = CustomValues {
    metadata: "example".to_string(),
    inner_vec: vec![],
};
values.push(42);
assert_eq!(values.metadata, "example");
assert_eq!(values.len(), 1);

§Variant Methods and Traits

For each variant, the following methods are generated for the wrapper:

MethodRequired TraitsDescription
all_*noneReturns true if all items are of this variant
any_*noneReturns true if any item is of this variant
count_*noneCounts all items of this variant
enumerate_*noneEnumerate items of this variant with their indices
enumerate_*_mutnoneEnumerate mutable items of this variant with their indices
first_*noneReturns reference to first item of this variant
first_*_mutnoneReturns mutable reference to first item of this variant
iter_*noneIterator over items of this variant
iter_*_mutnoneMutable iterator over items of this variant
last_*noneReturns reference to last item of this variant
last_*_mutnoneReturns mutable reference to last item of this variant

And the following traits for each variant with type V:

TraitRequired Trait(*)Description
Extend<V>Extend wrapper with items of this variant
From<&[V]>Default & CloneCreate wrapper from slice of this variant
From<&mut [V]>Default & CloneCreate wrapper from mutable slice
From<Vec<V>>DefaultCreate wrapper from Vec of this variant

(*) Default is required for the Vec wrapper, other traits are required for the enum.

§Game Inventory Example

This example demonstrates the power of the Polymorphic Vec in a game inventory context:

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
}

§Delegated Vec Methods and Traits

The vec wrapper implements many Vec methods and traits, with some modified to leverage nodyn features. Methods that directly delegate to slice methods are only implemented for custom wrappers as standard wrappers handle this using Deref and DerefMut.

MethodRequired Traits(*)Differences from Vec
appendnonenone; direct delegation
as_mut_slicenonenone; direct delegation
as_slicenonenone; direct delegation
binary_search_by_keynonenone; direct delegation
binary_search_bynonenone; direct delegation
binary_searchOrdnone; direct delegation
capacitynonenone; direct delegation
clearnonenone; direct delegation
clone_from_sliceClonenone; direct delegation
copy_from_sliceCopynone; direct delegation
copy_withinCopynone; direct delegation
dedup_by_keynonenone; direct delegation
dedup_bynonenone; direct delegation
dedupPartialEqnone; direct delegation
extend_from_sliceClonenone; direct delegation
extend_from_withinClonenone; direct delegation
extract_ifnonenone; direct delegation
fill_withnoneaccepts Into<enum>
fillClonenone; direct delegation
first_mutnonenone; direct delegation
firstnonenone; direct delegation
get_mutnonenone; direct delegation
getnonenone; direct delegation
insertnoneaccepts Into<enum>
into_boxed_slicenonenone; direct delegation
is_emptynonenone; direct delegation
is_sorted_by_keynonenone; direct delegation
is_sorted_bynonenone; direct delegation
is_sortedPartialOrdnone; direct delegation
iter_mutnonenone; direct delegation
iternonenone; direct delegation
last_mutnonenone; direct delegation
lastnonenone; direct delegation
lennonenone; direct delegation
newDefaultinitializes other fields with Default::default()
pop_ifnonenone; direct delegation
popnonenone; direct delegation
pushnoneaccepts Into<enum>
removenonenone; direct delegation
reserve_exactnonenone; direct delegation
reservenonenone; direct delegation
resizeCloneaccepts Into<enum>
retain_mutnonenone; direct delegation
retainnonenone; direct delegation
reversenonenone; direct delegation
rotate_leftnonenone; direct delegation
rotate_rightnonenone; direct delegation
shrink_to_fitnonenone; direct delegation
shrink_tononenone; direct delegation
sort_by_keynonenone; direct delegation
sort_bynonenone; direct delegation
sort_unstable_by_keynonenone; direct delegation
sort_unstable_bynonenone; direct delegation
sort_unstableOrdnone; direct delegation
sortOrdnone; direct delegation
splicenonenone; direct delegation
split_first_mutnonenone; direct delegation
split_firstnonenone; direct delegation
split_last_mutnonenone; direct delegation
split_lastnonenone; direct delegation
split_offDefaultinitializes other fields with Default::default()
swap_removenonenone; direct delegation
swapnonenone; direct delegation
to_vecClonenone; direct delegation
truncatenonenone; direct delegation
try_reserve_exactnonenone; direct delegation
try_reservenonenone; direct delegation
with_capacityDefaultinitializes other fields with Default::default()

(*) Default is required for the Vec wrapper, other traits are required for the enum.

traitrequired traits(*)differences from std::vec::Vec
AsMut<Self>nonereturns &mut self.
AsMut<Vec<enum>>nonedelegates to vec.
AsMut<[enum]>nonedelegates to vec.
AsRef<Self>nonereturns &self.
AsRef<Vec<enum>>nonedelegates to vec.
AsRef<[enum]>nonedelegates to vec.
Deref(**)none
DerefMut(**)none
Extend<enum>Clonedelegates to vec::extend.
From<&[enum]>Clone, Defaultinitializes other fields with default::default().
From<&mut [enum]>Clone, Defaultinitializes other fields with default::default().
From<Self>noneconverts to Vec<enum>.
From<Vec<enum>>Defaultinitializes other fields with default::default().
Fromiterator<enum>Defaultinitializes other fields with default::default().
IndexMutnonedelegates to vec::index_mut.
Indexnonedelegates to vec::index.
IntoIteratornoneimplemented for &self, &mut self, and self.

(*) Default is required for the Vec wrapper, other traits are required for the enum.

(**) Only implemented for standard vec wrappers.

§Feature Flags

Specify features within the macro using impl TryInto, impl is_as, impl introspection, or vec. These are disabled by default, allowing explicit control.

§Using Cargo Features (Deprecated)

If no impl features are specified, the macro falls back to Cargo feature flags for backward compatibility. All Cargo features are enabled by default.

Cargo Featureimpl EquivalentEnables
fromNoneFrom 0.2.0 no longer optional
try_intoTryIntoAutomatic TryFrom trait implementation
introspectionintrospectionType introspection methods (count, types, type_name)
is_asis_asVariant test and accessor methods (is_*, try_as_*)

To transition from Cargo features, replace feature flags in Cargo.toml with impl directives in the macro.

The cargo features will be removed in version 0.3.0.

§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);
}

Macros§

nodyn
Creates a wrapper enum for a set of types with automatic method and trait delegation.