2
votes

I'm having difficulty with the following code:

trait HelloPhrase {
    fn hello(&self, to: &'static str);
}

pub enum GetHelloResult<H: HelloPhrase> {
    Matched(H),
    NoMatch,
}

struct English;

impl English {
    pub fn new() -> English {
        English
    }
}

impl HelloPhrase for English {
    fn hello(&self, to: &'static str) {
        println!("Hello {}.", to)
    }
}

struct Phrases<H: HelloPhrase> {
    hello_phrases: std::collections::HashMap<&'static str, H>,
}

impl<H: HelloPhrase> Phrases<H> {
    pub fn new() -> Phrases<H> {
        Phrases { hello_phrases: std::collections::HashMap::new() }
    }

    pub fn add_hello_phrase(&mut self, lang: &'static str, hello_phrase: H) {
        self.hello_phrases.insert(lang, hello_phrase);
    }

    pub fn get_hello(&self, lang: &'static str) -> GetHelloResult<H> {
        match self.hello_phrases.get(lang) {
            Some(hello_phrase) => return GetHelloResult::Matched(hello_phrase),
            _ => return GetHelloResult::NoMatch,
        };
    }
}

fn main() {
    let mut hs = Phrases::new();
    hs.add_hello_phrase("english", English::new());

    match hs.get_hello("english") {
        GetHelloResult::Matched(hello_phrase) => hello_phrase.hello("Tom"),
        _ => println!("HelloPhrase not found"),
    }
}

(play link)

HelloPhrase is a trait for a language to implement, English, Russian, etc. Phrases is a manager struct, which could have many maps of language-to-phrase. This is a contrived example, but you could think of this as an event manager (that is, get the event handlers for X input), or as an HTTP handler and router.

With that said, I'm having difficulty understanding how to borrow ownership of a HelloPhrase to return it to the caller. Running it, returns the following error:

<anon>:40:66: 40:78 error: mismatched types:
 expected `H`,
    found `&H`
(expected type parameter,
    found &-ptr) [E0308]
<anon>:40             Some(hello_phrase) => return GetHelloResult::Matched(hello_phrase),
                                                                           ^~~~~~~~~~~~

I've tried adding:

pub fn get_hello(&self, lang: &'static str) -> GetHelloResult<&H> {

and

pub enum GetHelloResult<H: HelloPhrase> {
    Matched(&H),
    NoMatch,
}

(play link)

which results in the following error:

<anon>:7:13: 7:15 error: missing lifetime specifier [E0106]
<anon>:7     Matched(&H),

I'm having trouble adding a lifetime to the enum - in theory I want the lifetime of the returned value to be that of the Phrases struct - but the lifetime syntax is quite confusing to me so far. To sum this up into two questions:

  1. How do I add a lifetime to GetHelloResult, to satisfy this error?
  2. Based on ownership rules with Rust, is what I'm trying to do an anti-pattern with Rust? What might be a better design for something like this?

Based on the documentation, I know how to use a lifetime on a struct, but I do not know how to add a lifetime to an enum (syntax wise). I only mentioned struct lifetime because I assume that's a missing part, but I honestly don't know. Furthermore, if I add a lifetime to the struct and impl and attempt to add it to the hello_phrases map, I get the error

the parameter type `H` may not live long enough [E0309]
2
So i'm trying not to assume too much, and just going by what the error messages have led me to thus far. Which, so far has not mentioned anything about the struct fields lifetime.FizzBazer

2 Answers

3
votes

The confusion here is an unfortunate side effect of lifetime elision. It helps in 99% of cases, but isn't very discoverable.

You need to annotate GetHelloResult with a lifetime:

pub enum GetHelloResult<'a, H: 'a + HelloPhrase> {
    Matched(&'a H),
    NoMatch,
}

pub fn get_hello(&self, lang: &'static str) -> GetHelloResult<H> {
    match self.hello_phrases.get(lang) {
        Some(hello_phrase) => return GetHelloResult::Matched(hello_phrase),
        _ => return GetHelloResult::NoMatch,
    };
}

This ties the lifetime of the GetHelloResult to the lifetime of the Phrases struct, so that if the Phrases struct is mutated (or destructed!), the returned reference will be invalidated. The lifetime is inferred, in this case, to be the same as self, which isn't obvious by reading it, but true! In less obvious situations, you might want to explicitly annotate by using GetHelloResult<'a, H>.

Play link.

2
votes

The code works fine when you return a reference (fn get_hello(&self, lang: &'static str) -> GetHelloResult<&H>) so long as you implement the trait for references to types that also implement the trait:

impl<'a, H> HelloPhrase for &'a H 
    where H: HelloPhrase
{
    fn hello(&self, to: &'static str) {
        (**self).hello(to)
    }
}