For what it's worth, I've found a way to access it. But need some encapsulation. The trick is to use generics and homemade From/Into traits. There is how it can work (workspace styled example):
crates/common/src/lib.rs
// Use generic `T` to delegate to owner the "glue" type
pub struct System<T> {
pub components: Vec<Box<dyn Component<T>>>,
}
impl<T> System<T> {
// Example of usage of these components and how can they interact with `T`
pub fn work(&self) {
let work: Vec<Item<T>> = self.components.iter().map(|c| c.work()).collect();
for component in &self.components {
for item in &work {
component.react(item)
}
}
}
}
// Use generic `T` to delegate to owner the "glue" type
pub enum Item<T> {
Component(T),
}
pub trait Component<T> {
fn work(&self) -> Item<T>;
fn react(&self, item: &Item<T>);
}
// Declare a trait will be used to obtain the specific component type
pub trait FromComponentItem<T> {
fn from_component_item(value: T) -> Self;
}
// Declare a trait will be used to obtain, from "other components", a type knew by the component type
pub trait AsOtherComponentItem<T> {
fn as_other_component_item(&self) -> Option<T>;
}
crates/component-archives/src/lib.rs (a first component)
use common::{Component, FromComponentItem, AsOtherComponentItem, Item};
pub struct ArchivesComponent;
// This component indicate T must implement `FromComponentItem` and `AsOtherComponentItem` to permir conversions.
impl<T: FromComponentItem<ArchiverItem> + AsOtherComponentItem<SystemEvent>> Component<T>
for ArchivesComponent
{
fn work(&self) -> Item<T> {
Item::Component(T::from_component_item(ArchiverItem::ReadyToArchive))
}
// Example of how use Generic `Item` by in reality, use specific known type `SystemEvent`
fn react(&self, item: &Item<T>) {
match item {
Item::Component(item) => {
if let Some(item) = item.as_other_component_item() {
match item {
SystemEvent::Users(UsersSystemEvent::Created(user_name)) => {
println!("Archive user: {}", user_name)
},
SystemEvent::Users(UsersSystemEvent::Deleted(user_name)) => {
println!("Archive deleted user: {}", user_name)
}
}
}
}
}
}
}
pub enum ArchiverItem {
ReadyToArchive,
}
pub enum SystemEvent {
Users(UsersSystemEvent),
}
pub enum UsersSystemEvent {
Created(String),
Deleted(String),
}
crates/component-users/src.lib (a second component)
use common::{Component, FromComponentItem, Item};
pub struct UsersComponent;
impl<T: FromComponentItem<UsersItem>> Component<T> for UsersComponent {
fn work(&self) -> Item<T> {
Item::Component(T::from_component_item(UsersItem::CreatedUser("Frnck".into())))
}
fn react(&self, _item: &Item<T>) {}
}
pub enum UsersItem {
CreatedUser(String),
DeletedUser(String),
}
crates/runner/src/main.rs (all the glue done here, in the system owner)
use archives::{ArchiverItem, ArchivesComponent};
use common::{Component, FromComponentItem, AsOtherComponentItem, System};
use users::{UsersComponent, UsersItem};
// Convenient macro to declare the type which tale place of `T` and `FromComponentItem` implementations
macro_rules! items {
( $( [$name:ident, $event:ident] ),* ) => {
enum ComponentItem {
$( $name($event) ),*
}
$(
impl FromComponentItem<$event> for ComponentItem {
fn from_component_item(value: $event) -> Self {
ComponentItem::$name(value)
}
}
)*
};
}
// Shortcut to declare the conversion between components types
macro_rules! convert {
($target_type:ty, $outer_variant:path, { $($inner_pattern:pat => $result:expr),* $(,)? }) => {
impl AsOtherComponentItem<$target_type> for ComponentItem {
fn as_other_component_item(&self) -> Option<$target_type> {
#[allow(unreachable_patterns)]
match self {
ComponentItem::ArchivesComponent(_) => None,
$outer_variant(inner) => match inner {
$(
$inner_pattern => Some($result),
)*
_ => None,
},
_ => None,
}
}
}
};
}
// Building of the `T` type and `FromComponentItem` usages
items!(
[UsersComponents, UsersItem],
[ArchivesComponent, ArchiverItem]
);
// Building the conversion between components types
convert!(
archives::SystemEvent,
ComponentItem::UsersComponent,
{
UsersItem::CreatedUser(user_name) => archives::SystemEvent::Users(
archives::UsersSystemEvent::Created(user_name.clone())
),
UsersItem::DeletedUser(user_name) => archives::SystemEvent::Users(
archives::UsersSystemEvent::Deleted(user_name.clone())
)
}
);
fn main() {
let users = UsersComponent;
let archives = ArchivesComponent;
let c: Vec<Box<dyn Component<ComponentItem>>> = vec![Box::new(users), Box::new(archives)];
let system: System<ComponentItem> = System { components: c };
system.work();
}
A little bit sophisticated/complicated, but permit to keep component independent.