Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An Actor implementing multiple traits? #33

Closed
veniamin-ilmer opened this issue Sep 7, 2022 · 7 comments
Closed

An Actor implementing multiple traits? #33

veniamin-ilmer opened this issue Sep 7, 2022 · 7 comments

Comments

@veniamin-ilmer
Copy link

veniamin-ilmer commented Sep 7, 2022

In my emulator, I am making each chip actor run independent of each other, not reference each other directly.
I do this by having them only reference a parent trait actor any time they want to call! other actors.

The parent actor implementing this trait, will call! the other children actors.
Ideally, I would like this to be just one central motherboard actor, implementing all traits of all chips, with all chips stored in one struct.
But it seems like actor_of_trait is written in a way that forces me to choose just one trait.
Is it possible for me to make an actor which implements multiple traits, and pass this one central motherboard to all chip actors?
Otherwise, it looks like I will need a separate board actor implementing the trait for each chip.

@veniamin-ilmer
Copy link
Author

Providing concrete example of what I am talking about:

Here I define the parent's trait inside of the child.

So this way, the child only references the parent for all io requests. All chips would do this, so they don't communicate with each other. This way you can mix and match any chip to communicate with any other chip.

The glue between all the chips would be handled by one single parent, implementing all of these chips.

Here is an example board implementing the chip
Here is the same board implementing a different chip

But actor of trait seems to only allow for one trait
So I can pass it to here but not to here.

Does it make sense why I am interested in allowing for multiple traits for one actor?

@uazu
Copy link
Owner

uazu commented Sep 9, 2022

You can define a trait made up of other traits:

trait A: B + C {}

Then you could use actor_of_trait! with that trait.

However I suspect that this does not solve your problem, at least not immediately. I guess you want each chip to receive a reference to the board actor that lists only the traits that that chip requires, not all possible traits. So this means casting one trait to another. This may be possible, but I have not tried it. For example you need to cast Actor<Box<dyn ABCDTrait>> to Actor<Box<dyn BDTrait>> (where ABCDTrait is ATrait+BTrait+CTrait+DTrait, and BDTrait is BTrait + DTrait).

I wonder whether Rust is able to do these kinds of casts. It is from one dynamic trait to another, but still everything is static knowledge, at least locally to where the actor is created. It may be possible. If you look at the actor_of_trait! macro, there is no magic there. All the magic is done by Rust itself. So writing out the actor_of_trait! macro manually may allow you to try to adjust things to find a way. This is a general Rust question so maybe someone has found a way in another context.

@uazu
Copy link
Owner

uazu commented Sep 9, 2022

I tried the casting without success. The process of going from Box<MyType> to Box<dyn MyTrait> is called coercion. It's some magic that Rust does when it detects that it is required. It involves adding a vtable pointer outside the box, i.e. making it a fat pointer. However Actor<Box<MyType>> is similar to Rc<RefCell<Box<MyType>>>>, so the casting doesn't work, because it means adding a vtable pointer within an already-allocated cell. So right now I can't see any way to cast between different traits. (However maybe there is a way, but I can't see it right now.)

But certainly you can do the multiple-traits thing, by defining a single trait which includes all of them.

@veniamin-ilmer
Copy link
Author

veniamin-ilmer commented Sep 10, 2022

I was able to define:

trait AllTraits: Intel4004IOTrait + Intel4002IOTrait {}
type AllBox = Box<dyn AllTraits>;

let board = actor_of_trait!(stakker, AllBox, MC4::init(), ret_nop!());

However, I am not sure what to do from here. I understand Rust loses the ability to cast the trait once it is wrapped in something like a RefCell.

Reading online it seems like RefCell, and hence Actor can't cast due to invariance.
RefCell<T> is invariant over T.
https://rust-lang.github.io/rfcs/0738-variance.html
https://www.reddit.com/r/rust/comments/v9a9dq/comment/ibx8z8i/

Is there a way to briefly unwrap the Actor to cast the trait? Or somehow don't wrap the trait into an Actor until later?

Alternatively

Here is a proof of concept of how to can cast it with generics: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=21fa200c0aea421fea892e7334db616a

But I am not sure how to convert the test2<T: ATrait + ?Sized>(var: Rc<RefCell<Box<T>>>) into this:

pub type BoxedTrait = Box<dyn ATrait>;
pub trait ATrait {
  fn example_function(&self, cx: CX![BoxedTrait]);
}

Because it is no longer Box<dyn ..> anymore..

@uazu
Copy link
Owner

uazu commented Sep 10, 2022

I don't think it can be done with this approach. If you do Actor<Box<dyn Trait>>, the vtable is stored within stakker-internal structures. If somehow you could change it there, it would change for everyone referencing the actor. (Incidentally, Stakker doesn't use Rc<RefCell<...>>, but what it does use does the same job, so I mentioned that to make it easier to understand.)

However if you look at this alternate method of doing dyn Trait, that uses a wrapper. You could have several wrappers for different traits. Effectively Rust will create a different wrapper for each combination of external trait and board (since it is generic). The efficiency should be about the same, but the interface looks different. Although it doesn't look quite so clear in the code (actor calls look like normal calls), I wonder whether that could help.

To be honest in this kind of situation I've used the third method on that page, i.e. using Fwd. So I've not got to the point of having to solve these tricky trait problems.

It would be nice if we could find a clean way of doing this, but this faces Rust limitations. Maybe if one day I could implement Actor<dyn Trait> directly, then maybe the vtable pointer would be outside and that would solve the problem. But I don't think everything I need is implemented yet in Rust, especially unsized enums. I suppose I could have a go using ManuallyDrop, but it will require unsafe and will make a whole load of simple code more complicated. I may have a quick look to see if Rust will let me do this now, but previously the lack of unsized enums was the problem.

@veniamin-ilmer
Copy link
Author

Omg, that was a lot of indirection / boilerplate... But it works! Thank you!

I don't understand if I would be able to use Fwd! here.. It seems like it requires an ActorOwnAnon object, however this is a Child referring a parent, so the Child would need an actor object, not an actor own object..

@uazu
Copy link
Owner

uazu commented Sep 12, 2022

You don't need to have an ActorOwnAnon at all. That is only if you don't have anything else owning the actor, to keep it from being killed off. A Fwd is like an Actor reference combined with a bit of code to forward a call to that actor. So for creating interconnections between actors, it is ideal. One actor can pass the Fwd onto another and the actor doesn't need to know which other actor it is sending calls to at all. It is like a stream or channel, but there is no "close". Perhaps better see it more like an interconnect or a patch-cable. So you can definitely do what you need with Fwd, but maybe it is not convenient because you might need a lot of them if you traits have lots of methods (i.e. you'd need one for each trait method). But one advantage is that the calls don't need to go via the motherboard. They could go directly from one chip to another.

Sorry about the boilerplate. I was looking again at Actor<dyn Trait>, which is what you really want. Still I hit the same problem with Rust not allowing an unsized enum. However, if I use my unsized_enum crate, I think I could implement it. Perhaps I should do that, with an optional crate feature to enable it. But my unsized_enum crate has not been reviewed for soundness, so it's not really suitable for people who want maximum safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants