2
votes

I'm starting to get comfortable with Rust, but there are still some things that are really tripping me up with lifetimes. In this particular case, what I want to do is have an enum which may have different types wrapped as a generic parameter class to create strongly typed query parameters in a URL, though the specific use case is irrelevant, and return a conversion of that wrapped value into an &str. Here's an example of what I want to do:

enum Param<'a> {
   MyBool(bool),
   MyLong(i64),
   MyStr(&'a str),
}

impl<'a> Param<'a> {
    fn into(self) -> (&'static str, &'a str) {
        match self {
            Param::MyBool(b) => ("my_bool", &b.to_string()), // clearly wrong
            Param::MyLong(i) => ("my_long", &i.to_string()), // clearly wrong
            Param::Value(s) => ("my_str", s),
        }
    }
}

What I ended up doing is this to deal with the obvious lifetime issue (and yes, it's obvious to me why the lifetime isn't long enough for the into() function):

enum Param<'a> {
   MyBool(&'a str), // no more static typing :(
   MyLong(&'a str), // no more static typing :(
   MyStr(&'a str),
}

impl<'a> Param<'a> {
    fn into(self) -> (&'static str, &'a str) {
        match self {
            Param::MyBool(b) => ("my_bool", b),
            Param::MyLong(i) => ("my_long", i),
            Param::Value(s) => ("my_str", s),
        }
    }
}

This seems like an ugly workaround in a case where what I really want to do is guarantee the static typing of certain params, b/c now it's the constructor of the enum that's responsible for the proper type conversion. Curious if there is a way to do this... and yes, at some point I need &str as that is a parameter elsewhere, specifically:

let body = url::form_urlencoded::serialize(
               vec![Param::MyBool(&true.to_string()).
                       into()].
                   into_iter());

I went through a whole bunch of things like trying to return String instead of &str from into(), but that only caused conversion issues down the line with a map() of String -> &str. Having the tuple correct from the start is the easiest thing, rather than fighting the compiler at every turn after that.

-- update--

Ok, so I went back to a (String,String) tuple in the into() function for the enum. It turns out that there is an "owned" version of the url::form_urlencoded::serialize() function which this is compatible with.

pub fn serialize_owned(pairs: &[(String, String)]) -> String

But, now I'm also trying to use the same pattern for the query string in the hyper::URL, specifically:

fn set_query_from_pairs<'a, I>(&mut self, pairs: I) 
    where I: Iterator<Item=(&'a str, &'a str)>

and then I try to use map() on the iterator that I have from the (String,String) tuple:

params: Iterator<Item=(String, String)>

url.set_query_from_pairs(params.map(|x: (String, String)| -> 
    (&str, &str) { let (ref k, ref v) = x; (k, v) } ));

But this gets error: x.0 does not live long enough. Ref seems correct in this case, right? If I don't use ref, then it's k/v that don't live long enough. Is there something 'simple' that I'm missing in this?

1

1 Answers

3
votes

It is not really clear why you can't do this:

enum Param<'a> {
   MyBool(bool),
   MyLong(i64),
   MyStr(&'a str),
}

impl<'a> Param<'a> {
    fn into(self) -> (&'static str, String) {
        match self {
            Param::MyBool(b) => ("my_bool", b.to_string()),
            Param::MyLong(i) => ("my_long", i.to_string()),
            Param::MyStr(s) => ("my_str", s.into()),
        }
    }
}

(into() for &str -> String conversion is slightly more efficient than to_string())

You can always get a &str from String, e.g. with deref coercion or explicit slicing.