Search code examples
rustdecoratortraitsdecltype

Return object implementing multiple traits - decorator pattern


I am currently implementing decorator pattern in Rust. In Scala we could implement method chaining via traits like:

new Scanner
    with Whitespaces
    with Keywords

I want to do the same in Rust. So, different traits for scanner:

pub struct Lexer();

pub trait Scan {
    fn scan(&self, start: &String) -> DomainTags;
}

pub struct Keywords<T> {
    decorated: T
}
impl<T: Scan> Scan for Keywords<T> {
    fn scan(&self, start: &String) -> DomainTags {
        ...
    }
}

pub struct Whitespaces<T> {
    decorated: T
}
impl<T: Scan> Scan for Whitespaces<T> {
    fn scan(&self, start: &String) -> DomainTags {
        ...
    }
}

But since I want to build my lexers with method like:

pub fn build() -> ??? {
    let lex = Lexer();
    let lex = Keyword {
        decorated: Whitespaces {
            decorated: lex
        }
    };
    lex
}

I don't know if it is possible to statically deduce the return type to something like decltype(lex). What is the common approach, towards implementing the method? What could be improved?

To clarify: I want to return decltype(lex), because I may also have multiple traits for single Lexer, like:

pub trait Load {
    fn load<T : Load>(&self, keywords: &String);
}

impl Load for Lexer {
    fn load<Lexer>(&self, keyword : &String) {
       ...
    }
}

And I hope to return a decorated object with implementation of Load trait as well. Both method load and scan should be available.


Solution

  • A function can only return a single type of value, so the type returned by your function can't depend on runtime conditions. However that type may be a boxed trait, in which case the type of the value stored in the box may change provided it implements the appropriate trait (or traits).

    From the example code you've provided, I think that Lexer should be a trait like trait Lexer: Scan + Load {} (or maybe the Scan and Load traits don't need to exist at all and the scan and load methods can be defined directly in Lexer). Then your build function should just return a boxed Lexer:

    pub trait Lexer {
        fn scan (&self, start: &String) -> DomainTags;
        fn load (&self, keyword : &String);
    }
    
    pub struct Keywords<T> {
        decorated: T
    }
    impl<T: Lexer> Lexer for Keywords<T> {
        …
    }
    
    pub struct Whitespaces<T> {
        decorated: T
    }
    impl<T: Lexer> Lexer for Whitespaces<T> {
        …
    }
    
    pub fn build (cond: bool) -> Box<dyn Lexer> {
        if cond {
            Box::new (Whitespaces { … })
        } else {
            Box::new (Keywords { … })
        }
    }