Hi! I've recently run into a problem with my code design, I was wondering if anyone who's worked a lot with rust traits and enums could help out.
First, a bit of background: I'm designing a node-based modular synthesizer of sorts (think blender nodes or node-red). I decided to use electron to design the UI and rust to run the backend audio engine.
The main structure in my design is a node. Every node type is a unique struct that implements the Node
trait. Each node keeps track of its own internal state and has its own unique exposed methods (filter exposes a set_frequency
function, gain exposes a set_gain
function, etc). Nodes need to be both accessible in a generic way (node.list_input_socket_types
) and by specific type (gain_node.set_gain
). Generic, so that there's a unified UI experience, and specific to allow fast implementations when chaining nodes together and use static analysis as much as possible.
To start off my design, I created a Node
trait, that has a common API that all nodes must have (list_properties
, list_input_sockets
, etc). Now, to sync a node to the frontend (electron) I serialize the values returned from the required Node
methods into json and send it through a socket.
All is fine and well until I need to serialize the specifics of the structure (to save to a file). When serializing it to a file I need to account for the fact that each Node
has a unique underlying struct.
My first attempt was using enum that contains each of the possible node types, example:
#[derive(Serialize, Deserialize)]
pub enum NodeVariant {
GainGraphNode(GainGraphNode),
FilterGraphNode(FilterGraphNode),
MonoBufferPlayerNode(MonoBufferPlayerNode)
}
With this enum structure, I can use serde
to serialize and deserialize the different variants to disk. However, now I can't access the generic methods of the contained node, without doing something obnoxious like
match node_variant {
GainGraphNode(node) => node.list_properties(),
FilterGraphNode(node) => node.list_properties(),
MonoBufferPlayerNode(node) => node.list_properties()
}
which obviously isn't sustainable as I add more node types.
I want to be able to do something like node_variant.as_ref_to_node().list_properties()
, but I can't seem to figure out how I would do that. I could consume the enum and return a generic Node
, but then I can't use it as its specific type.
I considered doing something like
pub struct NodeVariantContainer {
variant: NodeVariant,
variant_as_node: Box<dyn Node>
}
But from my understanding of rust, that would require owning the node in both the enum and owned as a dyn Node
, thereby breaking the borrowing rules.
Is there a way to switch between referencing the node as a Node
, and referencing it as a NodeVariant
? Or some way to be able to both serialize the entire structure to disk and expose a generic interface across nodes to synchronize with the client? I'm open to redesigning everything if I've backed myself into a corner too