serde
serde copied to clipboard
feat: Extract serde_core out of serde crate
This PR implements approach suggested in #2584 by @epage and @soqb. Also https://github.com/serde-rs/serde/issues/2181 There were two changes initially that were since reverted during the lifetime of this PR:
- I've changed serde_derive to use
serde_coreinstead ofserde. This has since been reverted as it would be a breaking change. - Code examples in serde_core aliased serde_core as serde so that the docs reexported in serde refer to
serdeand notserde_core. This however was not necessary following commit 2258bb4
Before (11.4s for a clean release build with derive feature, 8 cores):
After (7.8s for a clean release build with derive feature, 8 cores):
Ugh, seems like there's a bunch of CI failures on set up job stage.
Besides the gains here, this allows serde_json and friends to depend on serde_core, preventing packages using serde w/ derive from serializing things.
There're merge conflicts on this branch again which needs to be resolved.
Hey, the merge conflicts on this PR are usually trivial to resolve (the PR itself doesn't change anything about the code in serde, just it's structure) and they'll pop up with each new serde version released; I am not sure if it makes sense to massage it all the time given that this PR has went almost unnoticed for 2 months now. I'd say it should be fine to resolve them once this PR gets some attention. =)
Hmmm I thought this PR is blocked because merge conflicts and would get some attentions once it is resolved, but it turns out that it just doesn't get much attention regardless of merge conflicts.
This PR would really help improve compilation time of serde with derive enabled and I was looking forward to it, it's sad that it has been blocking for so long was no review from maintainer.
Yeah, I think so too. On the other hand, it was not asked for so no hard feelings there. I would also love to see it merged.
But yeah, the conflicts will always be there whenever a new version of serde is released, as this PR moves half of the files into other directory (which git seems to be able to resolve conflicts for on it's own but GH can't see that) + the version number changes. Either way, if there's a consensus to merge it, we can just rebase the changes onto the current master and that should do it.
Two things:
- Rebases are preferred over merges. A rebase that only conflicts due to file moves is usually resolved trivially without user input
- nightly is broken, you may need to run with nightly, too and adjust some imports
- I'm not going to push for this in the next two months as I'm going on leave soon
- I can't count 😆
Hey, should we come back to this PR whenever you're available then? By that I also mean whether I should fix it up in the meantime or is it fine to just come back to it in 2 months and rebase it then?
I think it would be fine to just not rebase it. Either @dtolnay will pick it up at whatever state it is in while I'm gone and ask you to rebase, or I will when I'm back.
As a real-world example of how this can be beneficial, I was looking at the timings chart for ripgrep on the parallel frontend blog post. I did some analysis on it. I'm guessing this change on its own would take ripgrep build times on a 24-core machine from 9s to 6-7s.
After this change, what are the situations in which diagnostics that used to look like "the trait bound
T: serde::ser::Serializeis not satisfied" will instead say "the trait boundT: serde_core::ser::Serializeis not satisfied"? — these occur often when you are using a data format library like serde_json together with a data structure library whose Serde trait impls are behind a feature.
Not tested with serde_core (since more changes are needed) but forced some serde compiler errors to see what I'd get.
Using a type in a #[derive(Deserialize)] struct's field that doesn't implement Deserialize. Notice a lack of serde::. I even removed use serde::Deserialize to double check that wasn't the reason why.
error[E0277]: the trait bound `StringA: Deserialize<'_>` is not satisfied
--> crates/cargo-util-schemas/src/manifest.rs:30:25
|
30 | pub cargo_features: Option<Vec<StringA>>,
| ^^^^^^^^^^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `StringA`
|
= help: the following other types implement trait `Deserialize<'de>`:
bool
char
isize
i8
i16
i32
i64
i128
and 192 others
= note: required for `Vec<StringA>` to implement `Deserialize<'_>`
= note: 1 redundant requirement hidden
= note: required for `std::option::Option<Vec<StringA>>` to implement `Deserialize<'_>`
note: required by a bound in `next_element`
--> /home/epage/.cargo/registry/src/index.crates.io-6f17d22bba15001f/serde-1.0.196/src/de/mod.rs:1726:12
|
1724 | fn next_element<T>(&mut self) -> Result<Option<T>, Self::Error>
| ------------ required by a bound in this associated function
1725 | where
1726 | T: Deserialize<'de>,
| ^^^^^^^^^^^^^^^^ required by this bound in `SeqAccess::next_element`
Trait not in scope. Note the suggestion of use crate::manifest::_::_serde::Deserialize rather than serde::Deserialize. I suspect the compiler is picking up on an import generated by serde_derive.
error[E0599]: no function or associated item named `deserialize` found for struct `TomlInheritedField` in the current
scope
--> crates/cargo-util-schemas/src/manifest.rs:292:37
|
292 | TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit)
| ^^^^^^^^^^^ function or associated item not found in `TomlInheritedField`
...
444 | pub struct TomlInheritedField {
| ----------------------------- function or associated item `deserialize` not found for this struct
|
note: if you're trying to build a new `TomlInheritedField`, consider using `TomlInheritedField::new` which returns `To
mlInheritedField`
--> crates/cargo-util-schemas/src/manifest.rs:449:5
|
449 | pub fn new() -> Self {
| ^^^^^^^^^^^^^^^^^^^^
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
8 + use crate::manifest::_::_serde::Deserialize;
What alternative options exist, and how do they compare? Keep helper types in serde for now? Put helper types in a third crate somehow? Some feature voodoo? Progress on https://github.com/rust-lang/rust/issues/54363 would be valuable here too.
My assumption for this is the helpers would be in serde and we keep existing derive users working the same way we do today.
- No breaking change for now
- Isolate the semver compatibility guarentees
Maybe with serde@2, we could switch to serde_derive + serde_derive_macro where serde_derive has the helpers but doing that is dependent on a lot of different factors that don't seem worth discussing atm.
Just to add to second example provided by @epage; it looks like rustc may sometimes pick up a different path to (De)serialize trait, based on whether user has serde in non-macro scope. Baseline code:
use serde;
fn main() {
let _ = u32::deserialize("");
}
Stderr with `use serde`
error[E0599]: no function or associated item named `deserialize` found for type `u32` in the current scope
--> src/main.rs:7:23
|
7 | let _: u32 = u32::deserialize(());
| ^^^^^^^^^^^ function or associated item not found in `u32`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
2 + use serde::Deserialize;
|
For more information about this error, try `rustc --explain E0599`.
warning: `throwaway_serde` (bin "throwaway_serde") generated 1 warning
error: could not compile `throwaway_serde` (bin "throwaway_serde") due to previous error; 1 warning emitted
Stderr with `use serde_core`
error[E0599]: no function or associated item named `deserialize` found for type `u32` in the current scope
--> src/main.rs:7:23
|
7 | let _: u32 = u32::deserialize(());
| ^^^^^^^^^^^ function or associated item not found in `u32`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
2 + use serde_core::Deserialize;
|
For more information about this error, try `rustc --explain E0599`.
warning: `throwaway_serde` (bin "throwaway_serde") generated 1 warning
error: could not compile `throwaway_serde` (bin "throwaway_serde") due to previous error; 1 warning emitted
When both serde and serde_core are used, the one that's imported first is the one that's suggested in diagnostics.
As for the first example, could it be that rustc always picks "local" path to a (De)serialize trait, since the failure here is related to monomorphization/typeck?
When both serde and serde_core are used, the one that's imported first is the one that's suggested in diagnostics.
That's fine. When people are actually directly depending on serde_core, it's fine to suggest importing from it. Almost no one will be directly depending on serde_core. The fact that the import suggestions and other diagnostics always refer to serde is a good sign.
Using a type in a
#[derive(Deserialize)]struct's field that doesn't implementDeserialize. Notice a lack ofserde::. I even removeduse serde::Deserializeto double check that wasn't the reason why.
There are possibly other diagnostics that use the full path instead of just the reduced path, especially on older compiler versions. We've since moved diagnostics to refer to imported paths, but there may be some cases left where we report the full path (and that may then refer to serde_core). That's rare and in almost all cases a compiler bug, so we can ignore that.
So... the only remaining step is to consider putting various "fishy" Deserialize/Serialize impls (e.g. the one for Result) behind cargo features of serde_core, so that serde 2.0 or other future consumers can just not enable those features unless they deem them valid use cases after all.
This step requires going through every impl in serde_core and making a decision for whether to cargo-feature it or not. Most of them should be obviously fine (integers, atomics, string, vec, ...)
also next time you merge, maybe rebase and squash instead?
Outside of Result, I find impls for Mutex pretty suspicious; at the very least, I've never really found myself needing that + it's pretty subtle that the lock is essentially hidden away from you in a library code. That's just my 2 cents though.
Besides the gains here, this allows serde_json and friends to depend on serde_core, preventing packages using serde w/ derive from serializing things.
I wonder, is there a clear migration path for crates such as serde_json to start using serde_core in place of serde? Say the user depends on serde explicitly (using 1.0 as version constraint) - unless they decide to cargo update their lockfile, will current resolver prefer to have just one copy of serde that is serde_core-compliant, or is it possible that an user will end up with both jumbo serde (as a direct dependency of their code) and serde_core as a dependency of serde_json et al?
I wonder, is there a clear migration path for crates such as serde_json to start using serde_core in place of serde? Say the user depends on serde explicitly (using 1.0 as version constraint) - unless they decide to cargo update their lockfile, will current resolver prefer to have just one copy of serde that is serde_core-compliant, or is it possible that an user will end up with both jumbo serde (as a direct dependency of their code) and serde_core as a dependency of serde_json et al?
They could have a "never matching" cfg dependency on a new-enough serde that uses serde_core.
So what status of this? It is finished or not? If I understand correctly, the only thing remains is to add bunch of features, one for every manual Serialize / Deserialize implementation that will be in serde_core?
I don't have anything to add from my side (sans https://github.com/serde-rs/serde/pull/2608#issuecomment-2204657621, though I did not go ahead with this for the lack of feedback), so I'd expect it to be in a merge-able state - except for conflicts, that is.
@dtolnay, @oli-obk, it seems that all review comments was addressed here or at least @osiewicz thinks so. Please give your opinion.