Lifetime issue with borrowed string slice in same scope as String

I'm trying to write a function that takes a base64 encoded json string and returns a struct that derives serde::Deserialize but I'm running into lifetime - I find them very confusing and been trying to find a simple answer without success. This is the function:

    pub fn try_from_json_b64(json_b64: &String) -> Result<Cursor<T>, RepositoryError> {
        let octets = base64::decode(json_b64).map_err(|_| RepositoryError::CursorInvalid)?;
        let json = String::from_utf8(octets).map_err(|_| RepositoryError::CursorInvalid)?;
        // On the line below I get "'json' doesn't live long enough"
        serde_json::from_str::<Cursor<T>>(json.as_str()).map_err(|_| RepositoryError::CursorInvalid)
    }

I'm getting 'json' doesn't live long enough but from what I can see, all local variables in this function should live long enough, since they're not needed once the Cursor<T> is created and returned?

I'm getting a tip from the compiler that I don't understand how to interpret: argument requires that json is borrowed for 'static - but json is not in my function signature, so how can I specify a 'static lifetime for it?

What is Cursor<T>, and does it only implement Deserialize<'static>?

It's a local variable -- you can't borrow it for 'static.

1 Like

If you have any manual bounds that specify Deserialize they should probably be DeserializeOwned instead

2 Likes

what's considered a "manual bound"?

Im simply deriving Deserialize for the Cursor, and now tried with DeserializeOwned but it seems to give just the same result - what am I missing?

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Cursor<T: Serialize> {
    page: Page,
    limit: i64,
    value: T,
    sort_by: &'static str,
    sort_order: SortOrder,
}

// TODO!!
impl<T> Cursor<T>
where
    T: Serialize,
    T: DeserializeOwned,
    T: Copy,
    T: std::fmt::Debug,
{
    pub fn new(
        page: Page,
        limit: i64,
        value: T,
        sort_by: &'static str,
        sort_order: SortOrder,
    ) -> Self {
        Self {
            page,
            limit,
            value,
            sort_by,
            sort_order,
        }
    }

    pub fn try_from_json_b64(json_b64: &String) -> Result<Cursor<T>, RepositoryError> {
        let octets = base64::decode(json_b64).map_err(|_| RepositoryError::CursorInvalid)?;
        let json = String::from_utf8(octets).map_err(|_| RepositoryError::CursorInvalid)?;
        serde_json::from_str::<Cursor<T>>(json.as_str()).map_err(|_| RepositoryError::CursorInvalid)
    }
}

Deserializing a &'static str doesn't really make sense, at least not without intentionally leaking memory. An &'static str is usually present in the compiled binary. You could borrow the string directly from the json data but there are a bunch of caveats to that, so I wouldn't really recommend it either.

Just using a String works (I removed the types that weren't included in that snippet for simplicity)

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cursor<T: Serialize> {
    limit: i64,
    value: T,
    sort_by: String,
}

The Copy bound isn't compatible with holding a String though since it implements Drop

thanks, now it compiles and passes the tests. I'll have to spend some time to fully understand what the issue actually was as I still don't really get it - is it that deserializing the &'static str to json consumes the original b64-decoded string? Even though it takes a &String as arg..?
Still confused :smiley: but happy it runs

Deserializing takes the data from input (the base64 decoded data here) and builds Rust data structures with it. Your Cursor contained a &'static str which means it could only ever borrow literal bytes from the decoded input data, and that data had to stay valid as long as the decoded struct did. Since your string slice has a static lifetime the compiler was telling you that your input data didn't live long enough (it wasn't 'static because it would be deallocated when the function returned)

By using a String you instead copy the input bytes into a new data structure that actually owns the data rather than borrowing from the input.

Or, to put it more simply: &str means "somebody else is responsible for keeping this data around" and in this case the json variable is what was responsible for that. Since the json variable drops immediately there's no way you can return a struct that borrows from it.

3 Likes

Thanks for the explanation! When you describe it like that it all seems rather obvious :slight_smile:

1 Like