Repository: not-fl3/nanoserde (rust)

Issue:
Allow disabling support for unneeded serialization formats.
`nanoserde` supports several formats: RON, JSON, TOML, and a bincode-like binary format. For some cases I could imagine using more than one of these, but many programs may just need one or two. Including all of them seems to slows down the build a bit.

I think they should be behind e.g. `feature = "json"`, `feature = "ron"`, `feature = "bin"` and `feature = "toml`" (which would extend to the proc macro crate of course). I don't have strong feelings about which are on or off by default. (I don't think I'm the best person to argue either way there. Leaving them all on by default avoids breaking changes, though...).


Pull request:
put macros and traits of each type behind individual feature flags
Closes #12. Leaving this in draft for a while, pending feedback for or against. Will try to keep PR up to date with current master.

Some timing results on my computer:

Binary Only (clean builds, approximate)
- Avg. Debug: 0.8s
- Avg. Release: 0.8s

All Default Features (clean builds, approximate)
- Avg. Debug: 1.15s
- Avg. Release: 1.35s

So ~30% speedup on debug builds and ~40% on release at the cost of a minor version bump


Patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 219bc89..fe1da43 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -56,6 +56,58 @@ jobs:
           command: build
           args: --all-targets --target=${{ matrix.config.target }}
 
+  features:
+    name: Test Individual Features
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        feature: [
+          "binary",
+          "json",
+          "ron",
+          "toml",
+        ]
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          target: x86_64-unknown-linux-gnu
+          override: true
+
+      - uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --no-default-features --features ${{ matrix.feature }}
+
+  no_std_features:
+    name: Test Individual Features NoStd
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        feature: [
+          "binary",
+          "json",
+          "ron",
+          "toml",
+        ]
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          target: x86_64-unknown-linux-gnu
+          override: true
+
+      - uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --no-default-features --features "no_std, ${{ matrix.feature }}"
+
   test:
     name: Test
     runs-on: ${{ matrix.config.os }}
diff --git a/Cargo.toml b/Cargo.toml
index ef705bf..31233b0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nanoserde"
-version = "0.1.37"
+version = "0.2.0"
 authors = ["makepad <info@makepad.nl>", "Fedor <not.fl3@gmail.com>"]
 license = "MIT OR Apache-2.0"
 description = """
@@ -22,9 +22,13 @@ edition = "2018"
 repository = "https://github.com/not-fl3/nanoserde"
 
 [features]
-default = []
+default = ["json", "binary", "ron", "toml"]
+json = ["dep:nanoserde-derive", "nanoserde-derive/json"]
+binary = ["dep:nanoserde-derive", "nanoserde-derive/binary"]
+ron = ["dep:nanoserde-derive", "nanoserde-derive/ron"]
+toml = []
 no_std = ["dep:hashbrown"]
 
 [dependencies]
 hashbrown = { version = "0.12.3", optional = true }
-nanoserde-derive = { path = "derive", version = "=0.1.22" }
+nanoserde-derive = { path = "derive", version = "=0.2.0", optional = true }
diff --git a/README.md b/README.md
index 0cd16f2..c67121f 100644
--- a/README.md
+++ b/README.md
@@ -52,3 +52,17 @@ For more examples take a look at [tests](/tests)
 | container attribute: `#[nserde(proxy = "")]`   | yes    | yes   | no     | no    |
 | container attribute: `#[nserde(transparent)]`  | yes    | no    | no     | no    |
 
+## Crate features:
+
+All features are enabled by default. To enable only specific formats, import nanoserde using 
+```toml
+nanoserde = { version = "*", default-features = false, features = [] }
+```
+in your `Cargo.toml` and add one or more of the following crate features:
+
+| Format    | Feature Name   | 
+| ----------| -------------- |
+| Binary    | `binary`       | 
+| JSON      | `json`         | 
+| RON       | `ron`          |
+| TOML      | `toml`         |
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
index c52647d..30ea539 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nanoserde-derive"
-version = "0.1.22"
+version = "0.2.0"
 authors = ["Makepad <info@makepad.nl>", "Fedor <not.fl3@gmail.com>"]
 edition = "2018"
 description = "Fork of makepad-tinyserde derive without any external dependencies"
@@ -11,6 +11,9 @@ proc-macro = true
 
 [features]
 default = []
+json = []
+binary = []
+ron = []
 no_std = ["dep:hashbrown"]
 
 [dependencies]
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
index 4e8b620..c8a5e79 100644
--- a/derive/src/lib.rs
+++ b/derive/src/lib.rs
@@ -6,18 +6,24 @@ extern crate proc_macro;
 #[macro_use]
 mod shared;
 
+#[cfg(feature = "binary")]
 mod serde_bin;
+#[cfg(feature = "binary")]
 use crate::serde_bin::*;
 
+#[cfg(feature = "ron")]
 mod serde_ron;
+#[cfg(feature = "ron")]
 use crate::serde_ron::*;
 
+#[cfg(feature = "json")]
 mod serde_json;
+#[cfg(feature = "json")]
+use crate::serde_json::*;
 
 mod parse;
 
-use crate::serde_json::*;
-
+#[cfg(feature = "binary")]
 #[proc_macro_derive(SerBin, attributes(nserde))]
 pub fn derive_ser_bin(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse::parse_data(input);
@@ -37,6 +43,7 @@ pub fn derive_ser_bin(input: proc_macro::TokenStream) -> proc_macro::TokenStream
     ts
 }
 
+#[cfg(feature = "binary")]
 #[proc_macro_derive(DeBin, attributes(nserde))]
 pub fn derive_de_bin(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse::parse_data(input);
@@ -57,6 +64,7 @@ pub fn derive_de_bin(input: proc_macro::TokenStream) -> proc_macro::TokenStream
     ts
 }
 
+#[cfg(feature = "ron")]
 #[proc_macro_derive(SerRon, attributes(nserde))]
 pub fn derive_ser_ron(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse::parse_data(input);
@@ -76,6 +84,7 @@ pub fn derive_ser_ron(input: proc_macro::TokenStream) -> proc_macro::TokenStream
     ts
 }
 
+#[cfg(feature = "ron")]
 #[proc_macro_derive(DeRon, attributes(nserde))]
 pub fn derive_de_ron(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse::parse_data(input);
@@ -95,6 +104,7 @@ pub fn derive_de_ron(input: proc_macro::TokenStream) -> proc_macro::TokenStream
     ts
 }
 
+#[cfg(feature = "json")]
 #[proc_macro_derive(SerJson, attributes(nserde))]
 pub fn derive_ser_json(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse::parse_data(input);
@@ -114,6 +124,7 @@ pub fn derive_ser_json(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
     ts
 }
 
+#[cfg(feature = "json")]
 #[proc_macro_derive(DeJson, attributes(nserde))]
 pub fn derive_de_json(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse::parse_data(input);
diff --git a/derive/src/parse.rs b/derive/src/parse.rs
index 2ac4ee1..1122733 100644
--- a/derive/src/parse.rs
+++ b/derive/src/parse.rs
@@ -195,10 +195,12 @@ impl Generic {
         }
     }
 
+    #[cfg(any(feature = "binary", feature = "json"))]
     pub fn ident_only(&self) -> String {
         format!("{}{}", self.lifetime_prefix(), self.full())
     }
 
+    #[cfg(any(feature = "binary", feature = "json"))]
     pub fn full_with_const(&self, extra_bounds: &[&str], bounds: bool) -> String {
         let bounds = match (bounds, &self) {
             (true, Generic::Lifetime { .. }) => self.get_bounds().join(" + "),
@@ -412,6 +414,7 @@ impl Category {
 }
 
 impl Type {
+    #[cfg(any(feature = "ron", feature = "json"))]
     pub fn base(&self) -> String {
         let mut base = match &self.ref_type {
             Some(inner) => match inner {
diff --git a/derive/src/shared.rs b/derive/src/shared.rs
index aa4948e..110f814 100644
--- a/derive/src/shared.rs
+++ b/derive/src/shared.rs
@@ -1,5 +1,6 @@
 use alloc::string::String;
 
+#[cfg(any(feature = "binary", feature = "json"))]
 use crate::parse::{Enum, Struct};
 
 macro_rules! l {
@@ -22,6 +23,7 @@ pub fn attrs_proxy(attributes: &[crate::parse::Attribute]) -> Option<String> {
     })
 }
 
+#[cfg(any(feature = "ron", feature = "json"))]
 pub fn attrs_rename(attributes: &[crate::parse::Attribute]) -> Option<String> {
     attributes.iter().find_map(|attr| {
         if attr.tokens.len() == 2 && attr.tokens[0] == "rename" {
@@ -32,6 +34,7 @@ pub fn attrs_rename(attributes: &[crate::parse::Attribute]) -> Option<String> {
     })
 }
 
+#[cfg(any(feature = "ron", feature = "json"))]
 pub fn attrs_default(attributes: &[crate::parse::Attribute]) -> Option<Option<String>> {
     attributes.iter().find_map(|attr| {
         if attr.tokens.len() == 1 && attr.tokens[0] == "default" {
@@ -44,6 +47,7 @@ pub fn attrs_default(attributes: &[crate::parse::Attribute]) -> Option<Option<St
     })
 }
 
+#[cfg(any(feature = "ron", feature = "json"))]
 pub fn attrs_default_with(attributes: &[crate::parse::Attribute]) -> Option<String> {
     attributes.iter().find_map(|attr| {
         if attr.tokens.len() == 2 && attr.tokens[0] == "default_with" {
@@ -54,18 +58,21 @@ pub fn attrs_default_with(attributes: &[crate::parse::Attribute]) -> Option<Stri
     })
 }
 
+#[cfg(feature = "json")]
 pub fn attrs_transparent(attributes: &[crate::parse::Attribute]) -> bool {
     attributes
         .iter()
         .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "transparent")
 }
 
+#[cfg(feature = "json")]
 pub fn attrs_skip(attributes: &[crate::parse::Attribute]) -> bool {
     attributes
         .iter()
         .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "skip")
 }
 
+#[cfg(any(feature = "binary", feature = "json"))]
 pub(crate) fn struct_bounds_strings(struct_: &Struct, bound_name: &str) -> (String, String) {
     let generics: &Vec<_> = &struct_.generics;
 
@@ -90,6 +97,7 @@ pub(crate) fn struct_bounds_strings(struct_: &Struct, bound_name: &str) -> (Stri
     return (generic_w_bounds, generic_no_bounds);
 }
 
+#[cfg(any(feature = "binary", feature = "json"))]
 pub(crate) fn enum_bounds_strings(enum_: &Enum, bound_name: &str) -> (String, String) {
     let generics: &Vec<_> = &enum_.generics;
 
diff --git a/src/lib.rs b/src/lib.rs
index 3a65515..8384509 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,8 +7,6 @@
 //!
 //! Data serialization library with zero dependencies. No more syn/proc_macro2/quote in the build tree!
 //!
-//! `nanoserde` also almost do not use any generics, so build size is not going to be bloated with monomorphizated code.
-//!
 //! The main difference with "serde" and the reason why "nanoserde" is possible: there is no intermediate data model
 //! For each serialisation datatype there is a special macro.
 //!
@@ -20,20 +18,30 @@
 #![cfg_attr(feature = "no_std", no_std)]
 // Possibly stable in 1.65.
 // See: https://github.com/rust-lang/rust/pull/99917
+// and https://github.com/rust-lang/rust/issues/103765
 #![cfg_attr(feature = "no_std", feature(error_in_core))]
 
 extern crate alloc;
 
+#[cfg(any(feature = "binary", feature = "json", feature = "ron"))]
 pub use nanoserde_derive::*;
 
+#[cfg(feature = "binary")]
 mod serde_bin;
+#[cfg(feature = "binary")]
 pub use crate::serde_bin::*;
 
+#[cfg(feature = "ron")]
 mod serde_ron;
+#[cfg(feature = "ron")]
 pub use crate::serde_ron::*;
 
+#[cfg(feature = "json")]
 mod serde_json;
+#[cfg(feature = "json")]
 pub use crate::serde_json::*;
 
+#[cfg(feature = "toml")]
 mod toml;
+#[cfg(feature = "toml")]
 pub use crate::toml::*;


Test patch:
diff --git a/tests/bin.rs b/tests/bin.rs
index 355c020..27be065 100644
--- a/tests/bin.rs
+++ b/tests/bin.rs
@@ -1,3 +1,4 @@
+#![cfg(feature = "binary")]
 use std::{
     array,
     collections::{BTreeSet, LinkedList},
@@ -6,7 +7,6 @@ use std::{
 
 #[cfg(feature = "no_std")]
 use hashbrown::{HashMap, HashSet};
-
 #[cfg(not(feature = "no_std"))]
 use std::collections::{HashMap, HashSet};
 
diff --git a/tests/json.rs b/tests/json.rs
index 21e3e45..5275b6f 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -1,3 +1,4 @@
+#![cfg(feature = "json")]
 use nanoserde::{DeJson, SerJson};
 
 use std::{
diff --git a/tests/parse.rs b/tests/parse.rs
index 1f75dc9..76b3808 100644
--- a/tests/parse.rs
+++ b/tests/parse.rs
@@ -1,3 +1,4 @@
+#![cfg(all(feature = "binary", feature = "json", feature = "ron"))]
 use nanoserde::{DeBin, DeJson, DeRon, SerBin, SerJson, SerRon};
 
 // https://github.com/not-fl3/nanoserde/issues/83
diff --git a/tests/ron.rs b/tests/ron.rs
index 728e768..856a978 100644
--- a/tests/ron.rs
+++ b/tests/ron.rs
@@ -1,3 +1,4 @@
+#![cfg(feature = "ron")]
 use nanoserde::{DeRon, SerRon};
 
 use std::{
diff --git a/tests/ser_de.rs b/tests/ser_de.rs
index eca1293..4c37964 100644
--- a/tests/ser_de.rs
+++ b/tests/ser_de.rs
@@ -1,4 +1,10 @@
-use nanoserde::{DeBin, DeJson, DeRon, SerBin, SerJson, SerRon};
+#![cfg(any(feature = "binary", feature = "json", feature = "ron"))]
+#[cfg(feature = "binary")]
+use nanoserde::{DeBin, SerBin};
+#[cfg(feature = "json")]
+use nanoserde::{DeJson, SerJson};
+#[cfg(feature = "ron")]
+use nanoserde::{DeRon, SerRon};
 
 #[cfg(feature = "no_std")]
 use hashbrown::HashMap;
@@ -8,7 +14,10 @@ use std::collections::HashMap;
 
 #[test]
 fn ser_de() {
-    #[derive(DeBin, SerBin, DeJson, SerJson, DeRon, SerRon, PartialEq, Debug)]
+    #[derive(PartialEq, Debug)]
+    #[cfg_attr(feature = "binary", derive(DeBin, SerBin))]
+    #[cfg_attr(feature = "json", derive(DeJson, SerJson))]
+    #[cfg_attr(feature = "ron", derive(DeRon, SerRon))]
     pub struct Test {
         pub a: i32,
         pub b: f32,
@@ -32,15 +41,24 @@ fn ser_de() {
         g: (),
     };
 
-    let bytes = SerBin::serialize_bin(&test);
-    let test_deserialized = DeBin::deserialize_bin(&bytes).unwrap();
-    assert_eq!(test, test_deserialized);
+    #[cfg(feature = "binary")]
+    {
+        let bytes = SerBin::serialize_bin(&test);
+        let test_deserialized = DeBin::deserialize_bin(&bytes).unwrap();
+        assert_eq!(test, test_deserialized);
+    }
 
-    let bytes = SerJson::serialize_json(&test);
-    let test_deserialized = DeJson::deserialize_json(&bytes).unwrap();
-    assert_eq!(test, test_deserialized);
+    #[cfg(feature = "json")]
+    {
+        let bytes = SerJson::serialize_json(&test);
+        let test_deserialized = DeJson::deserialize_json(&bytes).unwrap();
+        assert_eq!(test, test_deserialized);
+    }
 
-    let bytes = SerRon::serialize_ron(&test);
-    let test_deserialized = DeRon::deserialize_ron(&bytes).unwrap();
-    assert_eq!(test, test_deserialized);
+    #[cfg(feature = "ron")]
+    {
+        let bytes = SerRon::serialize_ron(&test);
+        let test_deserialized = DeRon::deserialize_ron(&bytes).unwrap();
+        assert_eq!(test, test_deserialized);
+    }
 }
diff --git a/tests/toml.rs b/tests/toml.rs
index 4b1c821..c7c3c39 100644
--- a/tests/toml.rs
+++ b/tests/toml.rs
@@ -1,3 +1,5 @@
+#![cfg(feature = "toml")]
+
 #[cfg(feature = "no_std")]
 use hashbrown::HashMap;
 #[cfg(not(feature = "no_std"))]