diff --git a/.gitignore b/.gitignore index 0592392..54088d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target +target .DS_Store diff --git a/Cargo.toml b/Cargo.toml index 6a29672..1a6a71c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,4 @@ [workspace] resolver = "2" members = ["merde", "merde_json", "merde_time", "merde_core"] +exclude = ["zerodeps-example"] diff --git a/Justfile b/Justfile index 25545b0..a151121 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,14 @@ check: #!/bin/bash -eux + cargo check --example simple --no-default-features --features=json + cargo run --example simple --features=core,json cargo test -F full + cargo check --no-default-features cargo hack --feature-powerset check + + pushd zerodeps-example + cargo check + cargo check --features=merde + cargo tree --prefix none --no-dedupe | grep -v compact_str + cargo tree --prefix none --no-dedupe --features=merde | grep compact_str + popd diff --git a/merde/Cargo.toml b/merde/Cargo.toml index af1ee6c..1302797 100644 --- a/merde/Cargo.toml +++ b/merde/Cargo.toml @@ -31,8 +31,9 @@ merde_json = { version = "3.0.0", path = "../merde_json", optional = true } merde_time = { version = "3.0.0", path = "../merde_time", optional = true } [features] -default = ["core"] -full = ["json", "time", "core"] +default = ["core", "deserialize"] +full = ["core", "deserialize", "json", "time"] +deserialize = ["core"] core = ["dep:merde_core"] json = ["dep:merde_json"] time = ["dep:merde_time"] diff --git a/merde/README.md b/merde/README.md index dfe7107..8938d22 100644 --- a/merde/README.md +++ b/merde/README.md @@ -389,3 +389,109 @@ fn main() { ``` You can of course make your own newtype wrappers to control how a field gets deserialized. + +## Conditional compilation + +(As of merde 3.1), you never need to add `cfg` gates to conditionally invoke the `merde::derive!` +macro, because, with default features disabled, `merde` has zero dependencies. + +There's two main ways to be conservative with the amount of generated code / the amount of +dependencies pulled with merde. + +### Approach 1: "core" by default, "deserialize" on demand + +Your manifest could look like this: + +```toml +# in `Cargo.toml` + +[dependencies] +merde = { version = "3.1", default-features = false, features = ["core"] } +``` + +And then you'd be able to use merde-provided types, like `CowStr`: + +```rust +use merde::CowStr; + +#[derive(Debug)] +struct Person<'s> { + name: CowStr<'s>, + age: u8, // sorry 256-year-olds +} + +merde::derive! { + impl (ValueDeserialize, JsonSerialize, IntoStatic) for Person<'s> { name, age } +} +``` + +And the `impl` blocks for `ValueDeserialize`, and `JsonSerialize` wouldn't actually +be generated unless crates downstream of yours enable `merde/deserialize` or `merde/json`. + +### Approach 2: zero-deps + +If your manifest looks more like this: + +```toml +# in `Cargo.toml` + +[dependencies] +merde = { version = "3.1", default-features = false } + +[features] +default = [] +merde = ["merde/core"] +``` + +...with no `merde` features enabled by default at all, then you have to stay +away from merde types, or use substitutes, for example, you could switch +`CowStr<'s>` with `std::borrow::Cow<'s, str>` and get largely the same API: + +```rust +#[cfg(feature = "merde")] +use merde::CowStr; + +#[cfg(not(feature = "merde"))] +pub type CowStr<'s> = std::borrow::Cow<'s, str>; + +#[derive(Debug)] +pub struct Person<'s> { + name: CowStr<'s>, + age: u8, // sorry 256-year-olds +} + +merde::derive! { + impl (ValueDeserialize, JsonSerialize, IntoStatic) for Person<'s> { name, age } +} +``` + +(But not the same ABI! Careful if you use this in conjunction with something +like [rubicon](https://github.com/bearcove/rubicon)). + +With that configuration, users of your crate will only have to pay for downloading +`merde` and evaluating a few `derive!` macros which will produce empty token trees — +no extra dependencies, barely any extra build time. + +See `zerodeps-example` in the [merde repository](https://github.com/bearcove/merde) +for a demonstration: + +```shell +❯ cargo tree --prefix none +zerodeps-example v0.1.0 (/Users/amos/bearcove/merde/zerodeps-example) +merde v3.0.0 (/Users/amos/bearcove/merde/merde) +``` + +```shell +❯ cargo tree --prefix none --features 'merde' +zerodeps-example v0.1.0 (/Users/amos/bearcove/merde/zerodeps-example) +merde v3.0.0 (/Users/amos/bearcove/merde/merde) +merde_core v3.0.0 (/Users/amos/bearcove/merde/merde_core) +compact_str v0.8.0 +castaway v0.2.3 +rustversion v1.0.17 (proc-macro) +cfg-if v1.0.0 +itoa v1.0.11 +rustversion v1.0.17 (proc-macro) +ryu v1.0.18 +static_assertions v1.1.0 +``` diff --git a/merde/examples/simple.rs b/merde/examples/simple.rs index c37b716..4dda023 100644 --- a/merde/examples/simple.rs +++ b/merde/examples/simple.rs @@ -1,7 +1,13 @@ +#[cfg(feature = "core")] use merde::CowStr; -use merde::json::JsonSerialize; +#[cfg(not(feature = "core"))] +type CowStr<'s> = std::borrow::Cow<'s, str>; + +#[cfg(all(feature = "core", feature = "json"))] fn main() { + use merde::json::JsonSerialize; + let input = r#" { "name": "John Doe", @@ -30,7 +36,15 @@ fn main() { assert_eq!(person, person2); } -#[allow(dead_code)] +#[cfg(not(all(feature = "core", feature = "json")))] +fn main() { + eprintln!("Well if the `core` feature is not enabled,"); + eprintln!("we can't call `from_str_via_value` and stuff,"); + eprintln!("but this still serves as an example that"); + eprintln!("you can keep your `merde::derive!` in place,"); + eprintln!("they'll just not generate any code."); +} + #[derive(Debug, PartialEq, Eq)] struct Address<'s> { street: CowStr<'s>, @@ -48,7 +62,6 @@ merde::derive! { } } -#[allow(dead_code)] #[derive(Debug, PartialEq, Eq)] struct Person<'s> { name: CowStr<'s>, diff --git a/merde/src/lib.rs b/merde/src/lib.rs index 09790e7..f2908f7 100644 --- a/merde/src/lib.rs +++ b/merde/src/lib.rs @@ -19,6 +19,7 @@ pub use merde_core::*; #[macro_export] macro_rules! impl_value_deserialize { ($struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { + #[cfg(feature = "deserialize")] #[automatically_derived] impl<$lifetime> $crate::ValueDeserialize<$lifetime> for $struct_name<$lifetime> { @@ -49,6 +50,7 @@ macro_rules! impl_value_deserialize { }; ($struct_name:ident { $($field:ident),+ }) => { + #[cfg(feature = "deserialize")] #[automatically_derived] impl<'s> $crate::ValueDeserialize<'s> for $struct_name { @@ -83,6 +85,7 @@ macro_rules! impl_value_deserialize { #[macro_export] macro_rules! impl_into_static { ($struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { + #[cfg(feature = "core")] #[automatically_derived] impl<$lifetime> $crate::IntoStatic for $struct_name<$lifetime> { type Output = $struct_name<'static>; @@ -99,6 +102,7 @@ macro_rules! impl_into_static { }; ($struct_name:ident { $($field:ident),+ }) => { + #[cfg(feature = "core")] #[automatically_derived] impl $crate::IntoStatic for $struct_name { type Output = $struct_name; @@ -115,7 +119,7 @@ macro_rules! impl_into_static { #[macro_export] macro_rules! impl_json_serialize { ($struct_name:ident < $lifetime:lifetime > { $($field:ident),+ }) => { - #[cfg(feature = "json")] + #[cfg(all(feature = "core", feature = "json"))] #[automatically_derived] impl<$lifetime> $crate::json::JsonSerialize for $struct_name<$lifetime> { fn json_serialize(&self, serializer: &mut $crate::json::JsonSerializer) { @@ -131,7 +135,7 @@ macro_rules! impl_json_serialize { }; ($struct_name:ident { $($field:ident),+ }) => { - #[cfg(feature = "json")] + #[cfg(all(feature = "core", feature = "json"))] #[automatically_derived] impl $crate::json::JsonSerialize for $struct_name { fn json_serialize(&self, serializer: &mut $crate::json::JsonSerializer) { @@ -147,6 +151,37 @@ macro_rules! impl_json_serialize { }; } +#[doc(hidden)] +#[macro_export] +macro_rules! impl_trait { + // borrowed + (@impl JsonSerialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { + $crate::impl_json_serialize!($struct_name <$lifetime> { $($field),+ }); + }; + // owned + (@impl JsonSerialize, $struct_name:ident { $($field:ident),+ }) => { + $crate::impl_json_serialize!($struct_name { $($field),+ }); + }; + + // with lifetime param + (@impl ValueDeserialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { + $crate::impl_value_deserialize!($struct_name <$lifetime> { $($field),+ }); + }; + // l + (@impl ValueDeserialize, $struct_name:ident { $($field:ident),+ }) => { + $crate::impl_value_deserialize!($struct_name { $($field),+ }); + }; + + // with lifetime param + (@impl IntoStatic, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { + $crate::impl_into_static!($struct_name <$lifetime> { $($field),+ }); + }; + // without lifetime param + (@impl IntoStatic, $struct_name:ident { $($field:ident),+ }) => { + $crate::impl_into_static!($struct_name { $($field),+ }); + }; +} + /// Derives the specified traits for a struct. /// /// This macro can be used to generate implementations of [`JsonSerialize`], [`ValueDeserialize`], @@ -213,37 +248,6 @@ macro_rules! derive { (@step1 { } $struct_name:ident $fields:tt) => {}; } -#[doc(hidden)] -#[macro_export] -macro_rules! impl_trait { - // borrowed - (@impl JsonSerialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { - $crate::impl_json_serialize!($struct_name <$lifetime> { $($field),+ }); - }; - // owned - (@impl JsonSerialize, $struct_name:ident { $($field:ident),+ }) => { - $crate::impl_json_serialize!($struct_name { $($field),+ }); - }; - - // with lifetime param - (@impl ValueDeserialize, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { - $crate::impl_value_deserialize!($struct_name <$lifetime> { $($field),+ }); - }; - // l - (@impl ValueDeserialize, $struct_name:ident { $($field:ident),+ }) => { - $crate::impl_value_deserialize!($struct_name { $($field),+ }); - }; - - // with lifetime param - (@impl IntoStatic, $struct_name:ident <$lifetime:lifetime> { $($field:ident),+ }) => { - $crate::impl_into_static!($struct_name <$lifetime> { $($field),+ }); - }; - // without lifetime param - (@impl IntoStatic, $struct_name:ident { $($field:ident),+ }) => { - $crate::impl_into_static!($struct_name { $($field),+ }); - }; -} - #[cfg(test)] #[cfg(feature = "json")] mod json_tests { diff --git a/zerodeps-example/Cargo.lock b/zerodeps-example/Cargo.lock new file mode 100644 index 0000000..ec38a60 --- /dev/null +++ b/zerodeps-example/Cargo.lock @@ -0,0 +1,77 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "merde" +version = "3.0.0" +dependencies = [ + "merde_core", +] + +[[package]] +name = "merde_core" +version = "3.0.0" +dependencies = [ + "compact_str", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "zerodeps-example" +version = "0.1.0" +dependencies = [ + "merde", +] diff --git a/zerodeps-example/Cargo.toml b/zerodeps-example/Cargo.toml new file mode 100644 index 0000000..4f40dd5 --- /dev/null +++ b/zerodeps-example/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "zerodeps-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +merde = { version = "3.0.0", path = "../merde", default-features = false } + +[features] +default = [] +merde = ["merde/core"] diff --git a/zerodeps-example/src/lib.rs b/zerodeps-example/src/lib.rs new file mode 100644 index 0000000..d99e620 --- /dev/null +++ b/zerodeps-example/src/lib.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "merde")] +use merde::CowStr; + +#[cfg(not(feature = "merde"))] +pub type CowStr<'s> = std::borrow::Cow<'s, str>; + +#[derive(Debug)] +pub struct Person<'s> { + pub name: CowStr<'s>, + pub age: u8, // sorry 256-year-olds +} + +merde::derive! { + impl (ValueDeserialize, JsonSerialize, IntoStatic) for Person<'s> { name, age } +}