-1

I am implementing Rust's TryFrom trait for a serde_json::value::Value reference, and a function that generically converts a Vec<Value> into a Vec<T>, where T implements TryFrom<&Value>. Because I must specify a lifetime for the Value reference in my function, I cannot return the result of T::try_from (borrowed value is dropped).

I have tried an alternate approach that works; creating my own trait that is similar to TryFrom but without a generic. This works, but I don't understand why I can't use TryFrom and a generic, since the trait already exists.

My generic code which throws a compile-time error:

impl TryFrom<&Value> for Channel {
    type Error = &'static str;

    fn try_from(value: &Value) -> Result<Self, Self::Error> {
        let title = value.get("title").ok_or("couldn't find title property in json")?
            .as_str().ok_or("title was not a string")?;
        let name = value.get("user_name").ok_or("couldn't find user_name property in json")?
            .as_str().ok_or("user_name was not a string")?;

        Ok(Channel {
            title: String::from(title),
            user_name: String::from(name)
        })
    }
}

// I must add a lifetime below. Please ignore the self reference.
fn get_many_generic<'a, T: TryFrom<&'a Value>>(&self, url_str: &str) -> Result<Vec<T>, Box<dyn std::error::Error>> {
    // perform_get returns a serde_json::Value
    let mut value = &self.perform_get(url_str)?;

    if let Value::Object(map) = value {
        value = map.get("data").ok_or("found a map without a 'data' property when expecting an array")?;
    }

    if let Value::Array(vec) = value {
        Ok(vec.iter()
            .filter_map(|item|
                match T::try_from(item) {
                    Ok(model) => Some(model),
                    Err(e) => {
                        println!("Could not deserialize value {}", item);
                        None
                    }
                }
            ).collect())
    }
    else {
        Err(Box::new(
            Error::new(format!("Expected array from {}, but didn't receive one.", url_str))
        ))
    }
}

My code which works:

pub trait TryFromValue where Self: std::marker::Sized {
    fn try_from_value(value: &Value) -> Result<Self, Box<dyn Error>>;
}

impl TryFromValue for Channel {
    fn try_from_value(value: &Value) -> Result<Channel, Box<dyn Error>> {
        let title = value.get("title").ok_or("couldn't find title property in json")?
            .as_str().ok_or("title was not a string")?;
        let name = value.get("user_name").ok_or("couldn't find user_name property in json")?
            .as_str().ok_or("user_name was not a string")?;

        Ok(Channel {
            title: String::from(title),
            user_name: String::from(name)
        })
    }
}

fn get_many<T: TryFromValue>(&self, url_str: &str) -> Result<Vec<T>, Box<dyn std::error::Error>> {
    // perform_get returns a serde_json::Value
    let mut value = &self.perform_get(url_str)?;

    if let Value::Object(map) = value {
        value = map.get("data").ok_or("found a map without a 'data' property when expecting an array")?;
    }

    if let Value::Array(vec) = value {
        Ok(vec.iter()
            .filter_map(|item|
                match T::try_from_value(item) {
                    Ok(model) => Some(model),
                    Err(e) => {
                        println!("Could not deserialize value {}. Error {}", item, e);
                        None
                    }
                }
            ).collect())
    }
    else {
        Err(Box::new(
            Error::new(format!("Expected array from {}, but didn't receive one.", url_str))
        ))
    }
}

Why does this approach work, but the first code example fails?

  • 1
    What is the error message with the first piece of code? It's hard to answer this question because when I compile it I get a bunch of errors about missing imports and the incorrect `&self` argument, but I don't know what *you* see. Please try to create a [mre], ideally on [the playground](https://play.rust-lang.org/). – trent Nov 27 '19 at 02:18
  • I strongly suspect this to be a duplicate of [How do I write the lifetimes for references in a type constraint when one of them is a local reference?](https://stackoverflow.com/q/44343166/3650362) (similar questions: [How to write a trait bound for adding two references of a generic type?](https://stackoverflow.com/q/34630695/3650362), [Wrong trait chosen based on type parameter](https://stackoverflow.com/q/53125347/3650362) and [How to define lifetimes on a Fn trait bound returning references?](https://stackoverflow.com/q/53566761/3650362)) In each case the answer is to use an HRTB. – trent Nov 27 '19 at 02:26
  • Applied to your problem, that would look something like ``fn get_many_generic TryFrom<&'a Value>>(...)`` – trent Nov 27 '19 at 02:27
  • @trentcl You are right, this fixed my problem. I guess it's time to re-read that chapter in the Rustnomicon. Thank you! Also sorry for posting a dupe. – Chris Thomas Nov 27 '19 at 16:21
  • There's no reason to be sorry! Duplicate questions are sometimes useful; they can be linked together to help future askers find the answer more easily. Glad I was able to help! – trent Nov 27 '19 at 16:38

1 Answers1

-1

Higher rank trait bounds (suggested by trentcl) fixed this issue. See their comment.