Skip to content

Instantly share code, notes, and snippets.

@darconeous
Forked from rust-play/playground.rs
Last active May 17, 2019 23:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darconeous/d183e68edf34d7699af139a7d13db2b1 to your computer and use it in GitHub Desktop.
Save darconeous/d183e68edf34d7699af139a7d13db2b1 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
/// 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