Expand description
§Nested struct and enum definitions
One of the best parts of Rust’s ecosystem is serde
,
and how it allows to comfortably use native Rust types when working with
serialized data in pretty much any format.
Take this JSON object for example:
{
"name": "asdf",
"storage": {
"diskSize": "10Gi",
"storageTypes": {
"hdd": false,
"ssd": true
}
}
}
If you have some practice, you can probably immediately imagine a set of Rust structs which the JSON object could deserialize into:
struct Resource {
name: String,
storage: Storage,
}
struct Storage {
disk_size: String,
storage_types: StorageTypes,
}
struct StorageTypes {
hdd: bool,
ssd: bool,
}
Since Rust’s structs are “flat”, every JSON subobject needs its own struct, and they need to be typed out one next to the other, and not nested like the JSON object. This can get unwieldy for large objects with many fields and subobjects.
What if instead, you could just create your structs in the same nested style?
struct Resource {
name: String,
storage: struct {
disk_size: String,
storage_types: struct {
hdd: bool,
ssd: bool,
}
}
}
This crate allows you to do exactly that, at the expense of one macro.
§Usage
Wrap your nested struct into an invocation of structstruck::strike!
.
structstruck::strike! {
struct Outer {
inner: struct {
value: usize
}
}
}
This will expand to flat struct definitions:
struct Outer {
inner: Inner,
}
struct Inner {
value: usize
}
Since the inner struct’s name was not given, it was automatically inferred from the field name (similarly done for tuple enum variants).
The inferred name can be overwritten if necessary:
structstruck::strike! {
struct Outer {
inner: struct InNer {
value: usize
}
}
}
§Supported declarations
structstruck, despite its name, works with enums and structs, and with tuple and named variants.
structstruck::strike! {
struct Outer {
enum_demo: enum {
NamedVariant {
tuple_struct: struct (usize)
}
TupleVariant(struct InsideTupleVariant (isize))
}
}
}
This will generate the following declarations:
struct TupleStruct(usize);
struct InsideTupleVariant(isize);
enum EnumDemo {
NamedVariant { tuple_struct: TupleStruct },
TupleVariant(InsideTupleVariant),
}
§Substructs in generics
Declarations may appear inside generics arguments. (It works “as you would expect”.)
structstruck::strike! {
struct Parent {
a: Option<struct {
c: u32,
}>,
b: Result<
struct Then {
d: u64,
},
struct Else {
e: u128,
},
>
}
}
The above results in
struct A {
c: u32,
}
struct Then {
d: u64,
}
struct Else {
e: u128,
}
struct Parent {
a: Option<A>,
b: Result<Then, Else>,
}
(The structs themselves being generic is not supported yet(?).)
§Attributes
Applying attributes (or doc comments) to a single inner struct would be syntactically awkward:
structstruck::strike! {
struct Outer {
documented: /** documentation */ struct {},
attributed: #[allow(madness)] struct {},
}
}
Thus, structstruck
allows to use inner attributes at the start of the struct declarations and automatically transforms them to outer attributes
structstruck::strike! {
struct Outer {
documented: struct {
//! documentation
},
attributed: struct {
#![forbid(madness)]
},
}
}
To quickly apply attributes to all declarations, attributes can be wrapped in the #[structstruck::each[…]]
pseudoattribute.
structstruck::strike! {
// It's structstruck::each[…], not structstruck::each(…)
// This appears to confuse even the rustdoc syntax highlighter
#[structstruck::each[derive(Debug)]]
struct Parent {
a: Option<struct {
c: u32,
}>,
b: Result<
struct Then {
d: u64,
},
struct Else {
e: u128,
},
>
}
}
println!("{:#?}", Parent { ..todo!("value skipped for brevity") });
The behavior of each
can be influenced in two ways:
structstruck::exclude_each
will ignore any attributes ineach
for the current struct only.structstruck::clear_each
will ignore anystructstruck::each
from parent structs for the current struct and children.
The order of attributes does not matter.
For example:
structstruck::strike! {
struct A {
#![structstruck::each[deny(unused)]]
b: struct {
#![structstruck::each[allow(unused)]]
#![structstruck::skip_each]
#![structstruck::clear_each]
c: struct {}
}
}
}
will place no attributes on B
and only allow(unused)
on C
.
§Avoiding name collisions
If you want include the parent struct name (or parent enum name and variant name)
in the name of the child struct, add #[structstruck::long_names]
to the struct.
structstruck::strike! {
#[structstruck::long_names]
struct Outer {
inner: struct { value: usize }
}
}
This will generate the following declarations:
struct OuterInner {
value: usize
}
struct Outer {
inner: OuterInner,
}
This can be combined with structstruck::each
to use the full path of all ancestor struct names.
structstruck::strike! {
#[structstruck::each[structstruck::long_names]]
struct A {
b: struct {
c: struct { }
}
}
}
will generate three structs, named A
, AB
, and ABC
.
This is useful to prevent collisions when using the same field name multiple times or a type with the same name as a field exists.
§Missing features, limitations
- Generic parameter constraints need to be repeated for each struct.
- Usage error handling is minimal.
- rustfmt really doesn’t play along.
Macros§
- strike
- Main functionality