This is a very interesting alternative to flat structure I had before. Thank you very much for the clarification and the suggestion to not use the flat structure. I tried it out and came up with this data structure:
////////////////////////////////////////////////////////////////////////////////
// Tenant
pub struct Tenant<TR: TenantRelation> {
pub id : i32,
pub name : i32,
pub relations : TR,
}
// Trait
pub trait TenantRelation {}
// No relation
impl TenantRelation for () {}
// With "users"
struct TenantRelUsers<UR: UserRelation> {
users: Vec<User<UR>>
}
impl<UR: UserRelation> TenantRelation for TenantRelUsers<UR> {}
////////////////////////////////////////////////////////////////////////////////
// User
pub struct User<Rel: UserRelation> {
pub id : i32,
pub name : i32,
pub relations : Rel,
}
// Trait
pub trait UserRelation {}
// No relation
impl UserRelation for () {}
// With "tenant"
struct UserRelTenant<TR: TenantRelation> {
tenant: Tenant<TR>
}
impl<TR: TenantRelation> UserRelation for UserRelTenant<TR> {}
// With "tenant" and "projects"
struct UserRelTenantAndProjects<TR: TenantRelation, PR: ProjectRelation> {
tenant : Tenant<TR>,
projects: Vec<Project<PR>>,
}
impl<TR: TenantRelation, PR: ProjectRelation> UserRelation
for UserRelTenantAndProjects<TR, PR> {}
////////////////////////////////////////////////////////////////////////////////
// Project
struct Project<Rel: ProjectRelation> {
pub id : i32,
pub name : i32,
pub relations : Rel,
}
pub trait ProjectRelation {}
impl ProjectRelation for () {}
struct ProjectRelOwner<UR: UserRelation> {
owner: User<UR>
}
impl<UR: UserRelation> ProjectRelation for ProjectRelOwner<UR> {}
As this is a lot of boilerplate code I wrote macro to generate all of that for me:
Macro Definition
use paste::paste;
macro_rules! gen_entity_structs {
////////////////////////////////////////////////////////////////////////////
// Generate Relation
(@inner
>>> relation generator <<<
struct_name: $struct_n:ident;
struct_props: $($prop:ident: $prop_t:ty),*;
trait_name: $trait:ident;
struct $r_name:ident <
$ ($g:ident: $gt:ident),*
> {
$( $r_prop:ident: $r_prop_t:ty ),*
}
) => {
#[derive(Debug, Clone)]
pub struct $r_name < $($g: $gt),* > {
$( $r_prop: $r_prop_t ),*
}
impl <$($g: $gt),*> $trait for $r_name<$($g),*> {}
// Implement From trait to convert for example:
// Tenant<()> into UserRelTenant<()> or
// Vec<User<()>> into TenantRelUsers<()>
paste! {
#[allow(unused_parens)]
impl< $($g: $gt),* > From<($($r_prop_t),*)>
for $r_name < $($g),* > {
fn from (( $($r_prop),* ): ($($r_prop_t),*)) -> Self {
$r_name { $($r_prop),* }
}
}
}
// Implement From trait to convert for example:
// (User<()>, UserRelTenant<TR>) into User<UserRelTenant<TR>>
impl< $($g: $gt),* > From<($struct_n<()>, $r_name<$($g),*>)>
for $struct_n<$r_name<$($g),*>> {
fn from((a, b): ($struct_n<()>, $r_name<$($g),*>)) -> Self {
let $struct_n { $($prop),* ,.. } = a;
$struct_n { $($prop),* , relations: b }
}
}
// Implement From trait to convert for example:
// User<UserRelTenant<TR>> into User<()>
impl< $($g: $gt),* > From<$struct_n<$r_name<$($g),*>>>
for $struct_n<()> {
fn from(a: $struct_n<$r_name<$($g),*>>) -> Self {
let $struct_n { $($prop),* ,.. } = a;
$struct_n { $($prop),* , relations: () }
}
}
};
// Iteration: Split into head and tail, call with head, recurse with tail
(@inner
>>> iterate <<<
struct_name : $struct_n:ident;
struct_props : $($prop:ident: $prop_t:ty),*;
trait_name : $trait:ident;
struct $r1_name:ident < $ ($g1:ident: $gt1:ident ),* > {
$( $r1_prop:ident: $r1_prop_t:ty ),*
},
$(
struct $r_name:ident < $ ($g:ident: $gt:ident ),* > {
$( $r_prop:ident: $r_prop_t:ty ),*
}
),+
) => {
// Call relation generator
gen_entity_structs!(@inner
>>> relation generator <<<
struct_name : $struct_n;
struct_props: $($prop: $prop_t),*;
trait_name : $trait;
struct $r1_name < $ ($g1: $gt1 ),* > {
$( $r1_prop: $r1_prop_t ),*
}
);
// Continue iteration
gen_entity_structs!(@inner
>>> iterate <<<
struct_name : $struct_n;
struct_props: $($prop: $prop_t),*;
trait_name : $trait;
$(
struct $r_name < $ ($g: $gt ),* > {
$( $r_prop: $r_prop_t ),*
}
),*
);
};
// Iteration: End of iteration
(@inner
>>> iterate <<<
struct_name : $struct_n:ident;
struct_props : $($prop:ident: $prop_t:ty),*;
trait_name : $trait:ident;
struct $r1_name:ident < $ ($g1:ident: $gt1:ident ),* > {
$( $r1_prop:ident: $r1_prop_t:ty ),*
}
) => {
// Call relation generator
gen_entity_structs!(@inner
>>> relation generator <<<
struct_name : $struct_n;
struct_props: $($prop: $prop_t),*;
trait_name : $trait;
struct $r1_name < $ ($g1: $gt1 ),* > {
$( $r1_prop: $r1_prop_t ),*
}
);
};
////////////////////////////////////////////////////////////////////////////
// Entry point (without relations)
(
struct $name:ident {
$( $prop:ident: $prop_t:ty ),*
}
) => {
#[derive(Debug, Clone)]
pub struct $name {
$( pub $prop: $prop_t ),*
}
};
// Entry point (with relations)
(
struct $name:ident {
$( $prop:ident: $prop_t:ty ),*
}
relations [
$(
struct $r_name:ident < $ ($g:ident: $gt:ident ),* > {
$( $r_prop:ident: $r_prop_t:ty ),*
}
),+
]
// relations $relations:tt
) => {
paste!{
#[derive(Debug, Clone)]
pub struct $name<T: [<$name Relation>]> {
// Properties
$( pub $prop: $prop_t ),*,
// Relations field
pub relations: T
}
// Define trait "$nameRelation"
pub trait [<$name Relation>] {}
// Implement trait for ()
impl [<$name Relation>] for () {}
// Relations
gen_entity_structs!(@inner
>>> iterate <<<
struct_name : $name;
struct_props : $( $prop: $prop_t ),*;
trait_name : [<$name Relation>];
$(
struct $r_name < $ ($g: $gt ),* > {
$( $r_prop: $r_prop_t ),*
}
),+
);
} // end paste
};
////////////////////////////////////////////////////////////////////////////
}
Using the macro looks like this:
gen_entity_structs!(
struct Tenant {
id: i32,
name: i32
}
relations [
struct TenantRelUsers<UR: UserRelation> { users: Vec<User<UR>> }
]
);
gen_entity_structs!(
struct User {
id: i32,
name: i32
}
relations [
struct UserRelTenant<TR: TenantRelation> { tenant: Tenant<TR> },
struct UserRelTenantAndProjects<TR: TenantRelation, PR: ProjectRelation> {
tenant: Tenant<TR>,
projects: Vec<Project<PR>>
}
]
);
gen_entity_structs!(
struct Project {
id: i32,
name: i32
}
relations [
struct ProjectRelOwner<UR: UserRelation> {
owner: User<UR>
}
]
);
With this I got rid of the flat structure as you suggested and in addition to that the macro generates implementations of the From
trait to make conversions available. With these From
implementations I can for example do something like that:
// Create plain user
let plain_user: User<()> = User {
id: 1, name: 2, relations: ()
};
// Create UserRelTenant from Tenant
let user_tenant: UserRelTenant::<()> = Tenant {
id: 1, name: 2, relations: ()
}.into();
// Combine both to a User with "tenant" relation
let user_full: User<UserRelTenant<()>> =
(plain_user, user_tenant).into();
// Convert the User with "tenant" relation to a plain user again
let plain_user_2: User<()> = user_full.into();
// Create a tenant's "users" relation from a vector of plain users
let tenant_users: TenantRelUsers<()> = vec![
User { id: 1, name: 1, relations: () },
User { id: 2, name: 2, relations: () },
].into();
What do you think about this solution now? For me this is usable and pretty close to what I wanted to achieve. But does it have any downsides? I'm very excited to hear what you say 