I am trying to implement a tree structure in Rust. My structure is like a JSON object, where properties can be simple values or subtrees of other JSON objects.
For leaves, I have an enum called ShyScalar
:
#[derive(Clone, PartialEq, Debug)]
pub enum ShyScalar {
Boolean(bool),
Integer(i64),
Rational(f64),
String(String),
Error(String)
}
For nodes, I am trying to create another enum, ShyValue
. It can be a Scalar(ShyScalar)
or an Object
and I am trying to figure out how to implement the Object
part. The core of it is the trait ShyAssociation
, which lets me get or set properties using a string name, a crude form of reflection. Here is part of the implementation of the ShyAssociation
trait, with a sample implementation using a HashMap
.
pub trait ShyAssociation<'a> {
/// Set the named property to a new value and return the previous value.
/// If it is not permitted to set this property or it had no value previously, return None.
fn set(&mut self, property_name: &'a str, property_value: ShyValue) -> Option<ShyValue>;
/// Get the value of the named property.
/// If the property has no value or the property does not exist, return None.
fn get(&self, property_name: &'a str) -> Option<&ShyValue>;
/// True if is is possible to set the named property.
/// This may be true even if the property does not currently have a value.
fn can_set_property(&self, property_name: &'a str) -> bool;
/// True if the property currently has a value that can be retrieved, false otherwise.
fn can_get_property(&self, property_name: &'a str) -> bool;
}
impl<'a> ShyAssociation<'a> for HashMap<&'a str, ShyValue> {
fn set(&mut self, property_name: &'a str, property_value: ShyValue) -> Option<ShyValue> {
self.insert(property_name, property_value)
}
fn get(&self, property_name: &'a str) -> Option<&ShyValue> {
HashMap::get(self, property_name)
}
fn can_set_property(&self, _property_name: &'a str) -> bool {
true
}
fn can_get_property(&self, property_name: &'a str) -> bool {
self.contains_key(property_name)
}
}
This worked before the ShyValue
included the Object
variant.
I have tried using Box
, and am investigating Rc
and RefCell
, but do not know how to compose things so that I can implement the three essential traits for Object
: Clone
, PartialEq
and Debug
.
I was able to get Clone
working if I use the objekt crate, which implements ideas from How to clone a struct storing a boxed trait object?.
I cannot figure out how to compose things so as to implement all three traits.
The larger problem I am trying to solve is to build the context object (symbol table) for a rules engine. I need to be able to supply arbitrary objects that implement ShyAssociation
to permit the rules engine to extract values using dot notation and set the results back into the objects. I am trying to get around the fact that Rust does not support reflection.
Additional information.
When I use objekt_clonable crate and try to compose with PartialEq and Debug, I get these errors:
the trait
parser::shy_association::ShyAssociation
cannot be made into an objectthe trait
parser::shy_association::ShyAssociation
cannot be made into an objectnote: the trait cannot use
Self
as a type parameter in the supertraits or where-clausesrustc(E0038) <::objekt::macros::__internal_clone_trait_object macros>(38, 55): the traitparser::shy_association::ShyAssociation
cannot be made into an object
Here is some of the code I use:
use objekt_clonable::*;
#[clonable]
pub trait ShyAssociation<'a> : Clone + PartialEq + Debug {
/// Set the named property to a new value and return the previous value.
/// If it is not permitted to set this property or it had no value previously, return None.
fn set(&mut self, property_name: &'a str, property_value: ShyValue) -> Option<ShyValue>;
/// Get the value of the named property.
/// If the property has no value or the property does not exist, return None.
fn get(&self, property_name: &'a str) -> Option<&ShyValue>;
/// True if is is possible to set the named property.
/// This may be true even if the property does not currently have a value.
fn can_set_property(&self, property_name: &'a str) -> bool;
/// True if the property currently has a value that can be retrieved, false otherwise.
fn can_get_property(&self, property_name: &'a str) -> bool;
}