Adapter pattern via enum


#1

Hi Rustaceans!

Last days I was experimenting with Rust and was trying to implement some kind of universal variable type.
The idea is to have a type that I can initialize with a value of a several other types and get a value of a target type.
As an example can be dynamic to_string implementation for struct object. It can look like this (pseudo code):

struct MyStruct {
    val: i64,
    to_str: UniversalType,
}

impl MyStruct {
    fn to_string(&self) -> String { self.to_str.get(self) } 
}

let mut s = MyStruct { val: 3, to_str: "test" };
assert_eq!("test", s.to_string());

s.to_str = "cool";
assert_eq!("cool", s.to_string());

s.to_str = |s: MyStruct| -> String { format!("This is my struct: {}", s.val) };
assert_eq!("This is my struct: 3", s.to_string());

s.val = 42;
assert_eq!("This is my struct: 42", s.to_string());

I have implemented this as an adapter pattern via Rust enum and From trait.
Everything looks ok except casting closure using From trait.

Can anybody help with that error?

I have also created a macro that simplifies generation of such a structures so for the above example you can do something like this:

adapter_decl!(StringSource<obj: T>: String => String::default();
    Str: String {s => s.clone()};
    Func: Box<StringFunc<T>> {f => f(&obj)};
);

#2

You need to use as operator to coerce a monomorphic type Box<[closure]> into a trait object Box<Fn(&T) -> String>. (playground)

    s1.set_name(Box::new(|ms: &MyStruct| -> String { format!("s1:{}", ms.val) }) as StringFunc<MyStruct>);

(Also you don’t need to_string() after format macro, since format returns String)

Or, alternatively, you can change From impl for StringSource,

impl<T, F> From<F> for StringSource<T>
    where F: 'static /* <-- required for `Box`ing */ + Fn(&T) -> String
{
    fn from(f: F) -> Self {
        StringSource::Func(Box::new(f))
    }
}

and simply passing a closure to the set_name method. This may be a bit simpler. (playground)

    s1.set_name(|ms: &MyStruct| -> String { format!("s1:{}", ms.val) });

#3

It works! Thanks @sinkuu!


#4

Now I’m trying to update my macro to generate such a structures but got stuck on macro for From trait. See code here. It looks like it’s not allowed to use type identifiers in complex where statements.
Is it possible to solve/workaround this problem somehow?


#5

It seems like that you can workaround this by using tt matcher:

macro_rules! adapter_from {
    ($enum_name:ident, $in_name:ident, $($in_type_tok:tt)*) => (
        impl<T, F> From<F> for $enum_name<T> where F: 'static + $($in_type_tok)* {
            fn from(val: F) -> Self {
                $enum_name::$in_name(Box::new(val))
            }
        }
    );
}

#6

Thanks @sinkuu again!

So now with a the help of @sinkuu I can do following (playground):

adapter_decl!(StringSource<obj: T>: String => String::default();
    Str(String) {s => s.clone()};
    Func(Fn(&T) -> String) {f => f(&obj), boxed};
);

adapter_decl!(IntSource: i64 => i64::default();
    Int(i64) {s => *s};
    Func(Fn() -> i64) {f => f(), boxed};
);

// Usage
struct MyStruct {
    val: i64,
    name: StringSource<MyStruct>,
}

impl MyStruct {
    adapter_accessors!(get_name, set_name, name -> StringSource<Self>: String);
}

#[test]
fn test_adaptor_in_struct() {
    let mut s1 = MyStruct { val: 3, name: Default::default() };
    assert_eq!("", s1.get_name());

    s1.set_name("s1".to_string());
    assert_eq!("s1", s1.get_name());

    s1.set_name(|ms: &MyStruct| -> String { format!("s1:{}", ms.val) });
    assert_eq!("s1:3", s1.get_name());

    s1.val = 42;
    assert_eq!("s1:42", s1.get_name());
}

#[test]
fn test_adaptor_variable() {
    let mut a1 = IntSource::None;
    assert_eq!(0, a1.get());

    a1.set(7);
    assert_eq!(7, a1.get());

    a1.set(|| -> i64 { 42 });
    assert_eq!(42, a1.get());
}

It was nice experiment and I’m exited how flexible Rust is.

UPD. I’ve put a code in the separate repository.