-
-
Notifications
You must be signed in to change notification settings - Fork 803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why "The 'de lifetime should not appear in the type to which the Deserialize impl applies"? #2190
Comments
Probably https://users.rust-lang.org/t/how-to-end-borrow-in-this-code/72719/3?u=mingun can give you an answer:
But I totally agree that such an explanation should be placed in the documentation. |
Yeah, this imp of impl<'de: 'a, 'a> Deserialize<'de> for Q<'a> {} |
I'm also curious to see an example that does not compile with If there is not such example, then I would suggest removing this warning from the documentation and adding a |
Thanks for the link! However I believe this a recursive problem. If there was a #[derive(Deserialize)]
#[serde(lifetime = 'a)]
pub struct FileMetadata<'a> {
pub file_path: PathBuf,
pub last_updated: SystemTime,
#[serde(borrow)]
pub mediatype: MediaType<'a>,
} What I want to see is a problem using |
This is not the case. Here is a more complete example. use serde::de::{Deserialize, Deserializer};
use std::borrow::Cow;
#[derive(Debug)]
struct Pair<'a> {
array: Cow<'a, [&'a str]>,
string: &'a str,
}
impl<'de> Deserialize<'de> for Pair<'de> { // FAIL
//impl<'de: 'a, 'a> Deserialize<'de> for Pair<'a> { // ok
fn deserialize<D: Deserializer<'de>>(_deserializer: D) -> Result<Self, D::Error> {
unimplemented!()
}
}
pub fn repro<'de, D: Deserializer<'de>>(deserializer: D) -> Result<(), D::Error> {
let mut pair = Pair::deserialize(deserializer)?;
let string = pair.array.join(" ");
pair.string = &string;
println!("{:?}", pair);
Ok(())
} |
Thanks! This was what I was looking for. To sum up the reproduction requirements:
Breaking any of the requirements makes the example work. The requirements (1) and (3) can't be messed with because they are user-controlled. The requirement (2) could be circumvented by allowing lifetime parameters in functions to be smaller than the function scope. This doesn't work in Rust today, but assuming it would be possible, there would be another issue, which is that the function returns pub fn repro<'de, D>>(deserializer: D) -> Result<(), D::<'de>::Error> // hypothetic syntax
where D: for<'a> (Deserializer<'a> where 'de: 'a) // hypothetic syntax
{
let mut pair = Pair::deserialize(deserializer)?; // this would not compile
// because it return D::<'a>::Error instead of D::<'de>::Error So for types invariant in their lifetime, one has to dissociate the lifetime of the deserializer (covariant) from the one of the deserialized type (invariant) by using |
I disagree with this assessment. Conflating lifetimes in the impl for a covariant type makes the covariant type unusable in downstream impls, some of which may involve invariant lifetimes. So there are not any types that shouldn't follow the guidance on the website. The guidance on the website is accurate ("sooner or later you will be sad") and the rule is easy to follow. struct Bad<'a>(&'a str);
impl<'de> Deserialize<'de> for Bad<'de> {...}
#[derive(Deserialize)]
struct Thing<'a> {
array: Cow<'a, [&'a str]>,
bad: Bad<'a>,
} |
I believe this is a shortcoming of serde due to the use of intermediate/helper traits when implementing the deserialize function. This is fine and good practice by itself, however Rust assumes lifetime parameters of traits to be invariant, which prevents such code architecture (that's a shortcoming of Rust, but see the first section below for a generic work-around). Here is a formal proof of my claim using the Rust type system as a logic system. I copy and document it in the rest of this comment. Covariance helperThis could be a crate (with a /// Covariant type-level functions.
///
/// # Safety
///
/// - Self::Type<'a> must be covariant in 'a.
/// - Self::cast() must be the identity function.
unsafe trait Covariant {
type Type<'a>;
fn cast<'b, 'a: 'b>(x: Self::Type<'a>) -> Self::Type<'b> {
// SAFETY: Self::Type<'a> is covariant in 'a.
unsafe { core::mem::transmute(x) }
}
}
/// Proves that a type-level function is covariant.
///
/// `impl_covariant!(Name = for<'a> Type)` proves that `Type` (which may mention `'a`) is covariant
/// in `'a` and names the proof `Name`. This name is needed to safely cast `Type`.
macro_rules! impl_covariant {
($n:ident = for<$l:lifetime> $t:ty) => {
pub enum $n {}
// SAFETY: Self::cast() is the identity function, which proves that Self::Type<'a> is
// covariant in 'a by type-checking.
unsafe impl Covariant for $n {
type Type<$l> = $t;
fn cast<'b, 'a: 'b>(x: Self::Type<'a>) -> Self::Type<'b> {
x
}
}
};
} This macro only works when the type is not generic. When the type is generic like Deserialize trait for covariant types/// Simpler version of the Deserialize trait for covariant types.
///
/// There's no need for 2 lifetimes, because the result can always be cast to a shorter lifetime.
trait DeserializeCov: Covariant {
fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<Self::Type<'a>, D::Error>;
}
/// Implements the more complex version of the Deserialize trait for covariant types.
fn deserialize<'a, 'de: 'a, T: DeserializeCov, D: Deserializer<'de>>(
deserializer: D,
) -> Result<T::Type<'a>, D::Error> {
Ok(T::cast(T::deserialize(deserializer)?))
} This last function is actually the proof that one only needs Fixing the "Bad" examplepub struct Bad<'a>(pub &'a str);
impl<'a> Deserialize<'a> for Bad<'a> {
fn deserialize<D: Deserializer<'a>>(_: D) -> Result<Self, D::Error> {
unimplemented!()
}
}
impl_covariant!(BadCov = for<'a> Bad<'a>); // Bad is covariant
impl DeserializeCov for BadCov {
fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<Self::Type<'a>, D::Error> {
Bad::deserialize(deserializer)
}
}
// Helper for serde (I didn't manage to use `deserialize()` directly and had to instantiate it myself).
fn deserialize_bar<'a, 'de: 'a, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Bad<'a>, D::Error> {
deserialize::<'a, 'de, BadCov, D>(deserializer)
}
#[derive(Deserialize)]
pub struct Thing<'a> {
#[serde(borrow)] // not sure why this is needed, but it doesn't seem like it's hiding the issue
pub array: Cow<'a, [&'a str]>,
#[serde(deserialize_with = "deserialize_bar")] // commenting this shows which issue is being fixed
pub bad: Bad<'a>,
} Conclusion so farThere are a few things to consider when implementing
The remaining question for me is whether something is missing from that conclusion to characterize the cases when it's ok to use unified-lifetime versus bound-lifetime. That said, this conclusion is theoretical and not always practical (requiring the
So from a practical perspective, unless you're ready to fight Rust type system, you better use the bound-lifetime. That said, I'll continue using the unified-lifetime for learning purposes (and test the limits of Rust type system). But that's my problem. |
This PR is for discussion and related to serde-rs#2190. As discussed there, the benefit is not obvious (except for providing more control to the user). With this PR, a user can use `#[serde(lifetime = 'a)]` to control the lifetime of the implementation of `Deserialize`. In particular, the user is now able to control all the implementation parameters when using `#[serde(bound = "", lifetime = 'a)]`. See https://github.com/ia0/wasefire/blob/ccf7bc30ac8440b30981b2f39b734de10d1a037c/crates/protocol/src/lib.rs#L65 for an example in real code.
I would also like to note that the name let content: &'content str = "...";
let de = Deserializer::new(content); // borrows `content`
struct Type<'a>(&'a str);// expect to borrow from `content`, not from `de`
let data: Type<'data> = Type::deserialize(de)?;
// de is `Drop`ped, but `data` is live because it borrow from `content` rather that `de` |
Yes,
Currently, the
If both are false, then two lifetimes are needed and the conversion must happen inside the deserialization implementation when the lifetimes are only present covariantly (essentially For generality, the serde documentation recommends to always use two lifetimes (in case both the deserializer and the deserialized type are invariant). My claim is that the deserialized type is always covariant right after deserialization, it's just the Rust type system that doesn't know. Because right after deserialization, the only occurrences of the data lifetime within the deserialized type are pieces of the data, and thus covariant. The problem is that users usually conflate the concept of a structured view into serialized data and the concept of a data structure, by using the same type for both. The typical example is EDIT: The rest of this comment doesn't work. When implementing That said, I think serde decided to go with this design because when it was initially written, there was no generic associated type. Today, I wonder whether defining the trait Deserialize {
type Type<'a>;
fn deserialize<'a, 'de: 'a, D: Deserializer<'de>>(deserializer: D) -> Result<Self::Type<'a>, D::Error>;
} This would embed into the trait definition the fact that the deserialize implementation is converting from On the other side, now we need to ask the implementers to have |
Serde's documentation about lifetime mentioned this:
But didn't give a explanation, I'm curious about what's the drawback of using
'de
as the lifetime of type which Deserialize impl applies.The text was updated successfully, but these errors were encountered: