Search code examples
rusthyper

Using convert::Into with enum to unwrap and convert value


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?


Solution

  • 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.