Implementation of `Deserialize` is not general enough

I'm running into a serde issue with derive feature mixed with try_from annotation. My goal is to deserialize the FooWithStatic struct via the try_from implementation from RawFoo . I'm getting told the implementation of Deserialize is not general enough. I'm confused because RawFoo is all bound by the 'static lifetimeand my try_from implementation works just fine? It seems like this should be fine lifetime wise?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f17d2c465d32912eba5c3cfeff207dfe

use serde::{Serialize, Deserialize};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawFoo")]
struct FooWithStatic {
    name: &'static str
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct RawFoo {
    name: String,
}

struct FooNotFound;

impl std::fmt::Display for FooNotFound {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Foo not found!")
    }
}

impl TryFrom<RawFoo> for FooWithStatic {
    type Error = FooNotFound;

    fn try_from(raw_foo: RawFoo) -> Result<Self, Self::Error> {
        match raw_foo.name.as_str() {
            "foo" => Ok(FooWithStatic { name: &"foo" }),
            "bar" => Ok(FooWithStatic { name: &"bar" }),
            "baz" => Ok(FooWithStatic { name: &"baz" }),
            _ => Err(FooNotFound)
        }
    }
}

fn main() {
    let foo = FooWithStatic { name: "foo" };
    let serialized = serde_json::to_value(foo).unwrap();
    let deserialized: FooWithStatic = serde_json::from_value(serialized).unwrap();
    assert_eq!(foo, deserialized);
}

//    Compiling playground v0.0.1 (/playground)
// error: implementation of `Deserialize` is not general enough
//   --> src/main.rs:38:39
//    |
// 38 |     let deserialized: FooWithStatic = serde_json::from_value(serialized).unwrap();
//    |                                       ^^^^^^^^^^^^^^^^^^^^^^ implementation of `Deserialize` is not general enough
//    |
//    = note: `FooWithStatic` must implement `Deserialize<'0>`, for any lifetime `'0`...
//    = note: ...but it actually implements `Deserialize<'static>`

// error: could not compile `playground` due to previous error

The only way for serde to create a &'static str is to leak memory, and it refuses to do that. Use a String instead.

This looks like a "bug"/limitation in the derive implementation, although I am unsure if that can be easily fixed. One solution might be to allow borrow attributes with an empty value.

What happens is that the derive detects the string slice &'a str and automatically borrows. This bounds the 'de lifetime to outlive 'a. In your case 'a is 'static, so the implementation is actually impl Deserialize<'static> for FooWithStatic. This means that you can only call deserialize if the deserializer can borrow from 'static data, like a string literal or leaked strings.

But borrowing is not necessary in your case, since the TryFrom implementation converts the String into string literals, thus 'static. A more correct trait implementation for FooWithStatic might be

impl Deserialize<'de> for FooWithStatic
where
    RawFoo: Deserialize<'de>,

however, this does not work if RawFoo is private and FooWithStatic is public.

struct FooWithStatic {
    #[serde(borrow = "")]
    name: &'static str
}

This currently fails with error: at least one lifetime must be borrowed. This probably makes sense in the general case, as disabling the borrow just means that it is not possible to deserialize a string slice. However, in the special case of having a serde from/try_from it might not lead to too many misuses.

For now, you can implement deserialize manually.

impl<'de> Deserialize<'de> for FooWithStatic {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        RawFoo::deserialize(deserializer)?.try_into().map_err(D::Error::custom)
    }
}
1 Like

Placing #[serde(bound = "")] on FooWithStatic might work.

Edit: oh no, it doesn't, sorry. You should open a github issue for this.

You can "trick" serde into not being "smart" about the lifetimes with a type synonym.

type StaticStr = &'static str;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawFoo")]
struct FooWithStatic {
    name: StaticStr,
}

Rust Playground

Edit: I've found relevant documentation.

Deserializer lifetimes · Serde

Apparently, having a &str field automatically puts a #[serde(borrow)] attribute on that field, and there seems to be no good way to disable that. I'm not 100% sure on whether or not that attribute having any effect when try_from is used anyways is a goo thing.


By the way, it's also possible to use the existing re-exports of str i. e. &'static std::primitive::str to "trick" serde here, so you don't even need a type synonym.

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawFoo")]
struct FooWithStatic {
    name: &'static std::primitive::str,
}
4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.