In need of some software architecture help when using rust


#1

Hi there,

I’ve been playing around with rewriting some java libraries in rust, but I keep hitting a wall, probably because of my background. I’ve experience in C++ where I can do a lot of black magic using template metaprogramming + operator overloading and java where the magic comes from runtime reflection, and I’m having trouble avoiding the “shut up compiler, I know what I’m doing” way of thinking.

Here is the deal:

This particular java library defines a interface that allows client code to define managers for different kinds of objects. Say, if a object needs to be shared of the network then I can declare a Shared interface and implement a ManageShared class that extends from the Manager interface. Similarly, if a object is a signal from a hardware I can declare a Signal interface and implement a ManageSignal class. An object may be managed by more then one manager (i.e., it may implement Shared and Signal). The common hierarchy of managers allows us to create a dispatcher that sends the objects to their correct managers at run time (a ManagersManager, if you will (Java, am I right?)).

One of the biggest problems with the current state of this library is the insane amount of cyclical dependencies, singletons and race conditions caused by objects being initialized out of order in some circumstances. So as an exercise I’ve being slowly rewriting it in rust, mostly as an exercise. In general it’s been easy enough removing the cyclical dependencies and singletons, and the way rust language rules work forces me to rethink the way different parts of the system interact with each other. At the same time I’m trying to keep the concepts as close as possible to the original library as not to alienate possible users.

Story time over, here are my pain points:

  1. Objects that are shared over the network are serialized to json (serde_json) and sent over the wire. Now, on the other side how can I call the correct deserializer? The lack of runtime reflection in rust makes it so I can’t save the type of the object as a field during the serialization and call Its constructor through reflection on the other end. I’ve tried mapping the type identifier to a deserializer function but I can never get it quite to work…

  2. Like I said, the same object might be managed by many managers through different interfaces. I’ve been using trait objects to emulate that, but I can’t get everything to work together. Right now I’m mostly using Rc for simplicity sake (so I don’t have to deal with lifetimes), but I can’t get a object to be a Rc of trait object A on ManagerA and Rc of trait object B on ManagerB.

  3. I’m having trouble dealing with dynamically sending objects to the correct managers. Because I can’t check instanceof the type at runtime I don’t really know what to do…

Sorry about the wall of text. Hope someone can help me.

Just an observation: Rust has been incredible when writing something from scratch, but rewriting something from a managed languages might be the closest I’ve been to have a programming task make me cry.


#2

Interesting stuff (though sorry you almost cried). So when sending one if a few possible objects through serde isn’t the correct thing to do is wrap it in an enum of the possible types? This would create a tag that serde deserialize would be able to immediately understand. Or is this a scenario where the possible types aren’t known during compile time?

I guess your part 3 answers this.


#3

Regarding number 3, associating objects with managers, why not add an associated type to your trait, which will identify the type of the correct manager? Or perhaps a Rc pointing to the correct manager?


#4

The library user should be able to declare new types of objects and what managers should handle then, so the library don’t actually know at compilation time what types there are.


#5

I’m not sure how the associated type would work… Could you exemplify?

The Rc alternative… might be interesting, I have to think about it.


#6

As far as the associated types idea, I’m imagining something like:

trait Object {
    type MyManager: Manager;
    fn greet(&self);
}

trait Manager {
    type MyObject: Object;
    fn build_new() -> Self::MyObject;
}

let foo: Object = function_producing_trait_object();
let another_foo_with_same_type: Object = foo::MyManager::build_new();

So you can dynamically access the manager class of a given object (in this case to build another object of the same type, which is a bit gratuitous). So long as you don’t need to know the specific manager, this might do all you want, with minimal runtime overhead. All depends what the manager needs to be able to do.


#7

Maybe something like that can work, but I can see a few problems with that out of the top of my head:

At the very least Manager actually needs a method something like

fn register(obj: Rc<Object>); // or Rc<Self::MyObject>

And I can’t really see how, from a collection of Rc and Managers I would be able to dispatch objects correctly.

Furthermore, it would probably be better if the method was

fn register(&mut self, obj: Rc<Object>); // or Rc<Self::MyObject>

which would not work.

I guess keeping a Rc of the manager on the object is an option, but I’d have to figure out how to make it work with objects that have multiple managers…


#8

I agree, if you need several different managers for the same type of object, storing an Rc sounds like the best plan.


#9

If any one ends up here with the same questions:

i ended up using an ECS (particularly specs) since it worked well with the abstractions I work with