Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@ documentation = "https://docs.rs/struct_iterable"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]
members = [
"struct_iterable_derive",
"struct_iterable_internal",
]

[dependencies]
struct_iterable_derive = "0.1.0"
struct_iterable_internal = "0.1.1"
struct_iterable_derive = { path="struct_iterable_derive" }
struct_iterable_internal = { path="struct_iterable_internal" }

[lib]
name = "struct_iterable"
path = "src/lib.rs"

[[example]]
name = "custom-trait"
path = "examples/custom_trait.rs"

[[example]]
name = "any"
path = "examples/any.rs"

[package.metadata.docs.rs]
all-features = true
all-features = true
24 changes: 24 additions & 0 deletions examples/any.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use struct_iterable::Iterable;

// Use iterable attribut without custom item
// defaults to std::any::Any
#[derive(Iterable)]
struct MyStruct {
print_me: Option<String>,
do_not_print_me: Vec<u8>,
}

fn main() {
let my_struct = MyStruct {
print_me: Some("the test works".to_string()),
do_not_print_me: vec![4,2],
};

for (key, value) in my_struct.iter() {
if let Some(string_opt) = value.downcast_ref::<Option<String>>() {
if let Some(string) = string_opt.as_deref() {
println!("{key}=\"{string}\"");
}
}
}
}
20 changes: 20 additions & 0 deletions examples/custom_trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use struct_iterable::Iterable;

// Use the iterable attribute to derive a custom trait instead
#[derive(Iterable)]
#[iterable(ToString)]
struct MyStruct {
a: u32,
b: String,
}

fn main() {
let my_struct = MyStruct {
a: 42,
b: String::from("foobar"),
};

for (key, value) in my_struct.iter() {
println!("{key}={}", value.to_string());
}
}
6 changes: 3 additions & 3 deletions struct_iterable_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ proc-macro = true
[dependencies]
syn = "2.0.13"
quote = "1.0.26"
proc-macro2 = "1.0.56"
erased-serde = "0.3.7"
struct_iterable_internal = "0.1.1"
proc-macro2 = "1.0.79"
erased-serde = "0.4.4"
struct_iterable_internal = "0.1.1"
38 changes: 26 additions & 12 deletions struct_iterable_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
use struct_iterable_internal::Iterable;
use syn::{Meta, parse_macro_input, Data, DeriveInput, Fields};

/// The `Iterable` proc macro.
///
Expand Down Expand Up @@ -37,13 +36,28 @@ use struct_iterable_internal::Iterable;
/// };
///
/// for (field_name, field_value) in my_instance.iter() {
/// println!("{}: {:?}", field_name, field_value);
/// println!("{field_name}: {field_value:?}");
/// }
/// ```
#[proc_macro_derive(Iterable)]
#[proc_macro_derive(Iterable, attributes(iterable))]
pub fn derive_iterable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

let trait_name = match input.attrs.iter().find(
|a| a.path().segments.len() == 1 && a.path().segments[0].ident == "iterable"
) {
Some(attr) => {
if let Meta::List(meta) = &attr.meta {
meta.tokens.clone()
} else {
panic!("Invalid format of \"iterable\" attribute");
}
},
None => {
quote!(std::any::Any)
},
};

let struct_name = input.ident;
let fields = match input.data {
Data::Struct(data_struct) => match data_struct.fields {
Expand All @@ -57,19 +71,19 @@ pub fn derive_iterable(input: TokenStream) -> TokenStream {
let field_ident = &field.ident;
let field_name = field_ident.as_ref().unwrap().to_string();
quote! {
(#field_name, &(self.#field_ident) as &dyn std::any::Any)
(#field_name, &(self.#field_ident) as &dyn #trait_name)
}
});

let expanded = quote! {
impl Iterable for #struct_name {
fn iter<'a>(&'a self) -> std::vec::IntoIter<(&'static str, &'a dyn std::any::Any)> {
TokenStream::from(quote! {
impl<'a> struct_iterable::Iterable for &'a #struct_name {
type Item = &'a dyn #trait_name;

fn iter(self) -> std::vec::IntoIter<(&'static str, Self::Item)> {
vec![
#(#fields_iter),*
].into_iter()
}
}
};

TokenStream::from(expanded)
}
})
}
6 changes: 4 additions & 2 deletions struct_iterable_internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
/// }
/// ```
pub trait Iterable {
type Item;

/// Returns an iterator over the struct's fields as tuples.
///
/// Each tuple contains a field's name as a static string and a reference to the field's value as `dyn Any`.
Expand All @@ -52,5 +54,5 @@ pub trait Iterable {
/// println!("{}: {:?}", field_name, field_value);
/// }
/// ```
fn iter(&self) -> std::vec::IntoIter<(&'static str, &'_ dyn std::any::Any)>;
}
fn iter(self) -> std::vec::IntoIter<(&'static str, Self::Item)>;
}