-
-
Save darconeous/d183e68edf34d7699af139a7d13db2b1 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Demonstration of a way to do polymorphic property lookup of standardized | |
/// collections of properties via simple traits and getter methods. This | |
/// approach could be extended to do things like fetch a list of the supported | |
/// keys, or expose aritrary traits via a property interface. | |
/// This is the trait that will be our general-purpose property interface. It | |
/// is intended to be used as a trait object. | |
/// | |
/// This trait is implemented automatically by the `impl_super_trait!` macro, | |
/// defined below. | |
pub trait SuperTrait { | |
fn get_property(&self, key: &str) -> Result<String, ()>; | |
} | |
/// Utility trait to aggregate lookups. This trait isn't used directly, instead | |
/// it is used by the variants to dispatch individual property requests after | |
/// the specific variant owning the property has been determined. | |
pub trait TraitGlue<T> | |
where | |
T: ?Sized, | |
{ | |
fn get_sub_property(&self, key: &str) -> Result<String, ()>; | |
} | |
/// Module for defining "Variant A" properties. | |
/// In some applications of this technique, this module could be created | |
/// using a macro. | |
mod variant_a { | |
/// The trait to implement if you want to support the properties | |
/// in this variant. | |
pub trait Trait { | |
fn get_foo(&self) -> String; | |
fn get_bar(&self) -> String; | |
} | |
/// This is effectively the "path" of the properties in this variant. | |
pub const VARIANT_ID: &str = "A"; | |
// Each variant gets their own implementation of `TraitGlue`. | |
// Note that using `super::TraitGlue<T>` here would not work. | |
impl<T: Trait> super::TraitGlue<Trait> for T { | |
fn get_sub_property(&self, key: &str) -> Result<String, ()> { | |
match key { | |
"foo" => Ok(self.get_foo()), | |
"bar" => Ok(self.get_bar()), | |
_ => Err(()), | |
} | |
} | |
} | |
} | |
/// Module for defining "Variant B" properties. | |
/// This is just like "Variant A", but with different properties. | |
mod variant_b { | |
/// The trait to implement if you want to support the properties | |
/// in this variant. | |
pub trait Trait { | |
fn get_widget(&self) -> String; | |
fn get_fuzz(&self) -> String; | |
} | |
/// This is effectively the "path" of the properties in this variant. | |
pub const VARIANT_ID: &str = "B"; | |
// Each variant gets their own implementation of `TraitGlue`. | |
// Note that using `super::TraitGlue<T>` here would not work. | |
impl<T: Trait> super::TraitGlue<Trait> for T { | |
fn get_sub_property(&self, key: &str) -> Result<String, ()> { | |
match key { | |
"widget" => Ok(self.get_widget()), | |
"fuzz" => Ok(self.get_fuzz()), | |
_ => Err(()), | |
} | |
} | |
} | |
} | |
/// This macro implements `SuperTrait::get_property()` for the specified | |
/// struct and given variant modules. The syntax is: | |
/// ``` | |
/// impl_super_trait!(<STRUCT-NAME> { <VARIANT-MODULE>, ... }); | |
/// ``` | |
macro_rules! impl_super_trait { | |
($backing_struct:ident { $( $T:ident ),* }) => { | |
impl SuperTrait for $backing_struct { | |
fn get_property(&self, key: &str) -> Result<String,()> { | |
match key.split_at(key.find("/").unwrap_or(0)) { | |
$( ($T::VARIANT_ID,subkey) => | |
TraitGlue::<$T::Trait>::get_sub_property(self, &subkey[1..]), | |
)* | |
_ => Err(()), | |
} | |
} | |
} | |
}; | |
// Handle dangling comma case. | |
($backing_struct:ident {$( $t:ident ),+ ,}) => { | |
impl_super_trait!($backing_struct { $( $t ),* }); | |
}; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// USAGE EXAMPLE ////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////// | |
// Our data backing struct. Super simple for the sake of clarity. | |
pub struct MyDataBacking(); | |
impl variant_a::Trait for MyDataBacking { | |
fn get_foo(&self) -> String { | |
return "THIS IS FOO".to_string(); | |
} | |
fn get_bar(&self) -> String { | |
return "THIS IS BAR".to_string(); | |
} | |
} | |
impl variant_b::Trait for MyDataBacking { | |
fn get_widget(&self) -> String { | |
return "THIS IS WIDGET".to_string(); | |
} | |
fn get_fuzz(&self) -> String { | |
return "THIS IS FUZZ".to_string(); | |
} | |
} | |
// This macro call builds our `SuperTrait` implementation. Notice | |
// that the macro arguments specify the name of the struct as | |
// well as a "list" of implemented variants (identified by module). | |
// If a variant trait is implemented but not listed here it will be | |
// not be accessable via `SuperTrait::get_property()`. | |
impl_super_trait!( MyDataBacking { | |
variant_a, | |
variant_b, | |
} ); | |
/////////////////////////////////////////////////////////////////////////////// | |
// TEST CASE ////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////// | |
fn main() { | |
let backing = MyDataBacking(); | |
let keys = [ | |
"A/foo", "A/bar", "B/widget", "B/fuzz", "C/blah", "A/widget", "crabs", | |
]; | |
for key in &keys { | |
let value = backing.get_property(key); | |
println!(r#" "{}": {:?} "#, key, value); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment