Repository: not-fl3/nanoserde
Pull request #104 (2024-05-27 16:33:07)

remove no_std hashbrown dep
Hashbrown added several recursive deps, time to rip it out

not-fl3: Fun fact: hashbrown now depends on syn and whatnot, which kind of eleminates the whole purpose of nanoserde

```
│   ├── hashbrown v0.13.2
│   │   └── ahash v0.8.11
│   │       ├── cfg-if v1.0.0
│   │       ├── once_cell v1.19.0
│   │       └── zerocopy v0.7.34
│   │           └── zerocopy-derive v0.7.34 (proc-macro)
│   │               ├── proc-macro2 v1.0.82
│   │               │   └── unicode-ident v1.0.12
│   │               ├── quote v1.0.36
│   │               │   └── proc-macro2 v1.0.82
│   │               │       └── unicode-ident v1.0.12
│   │               └── syn v2.0.63
│   │                   ├── proc-macro2 v1.0.82
│   │                   │   └── unicode-ident v1.0.12
│   │                   ├── quote v1.0.36
│   │                   │   └── proc-macro2 v1.0.82
│   │                   │       └── unicode-ident v1.0.12
│   │                   └── unicode-ident v1.0.12
│   │       [build-dependencies]
│   │       └── version_check v0.9.4
```

never trust a crates.io dependency, they can brought a full rust parser into your dependency at literally any moment :D 

(not a no-std user here, no opinion on what to do about it)
knickish: Gah thats not cool. Hmmm. Maybe we should just drop hashmap impl when doing no_std?
knickish: I guess that wouldn't be additive though :/
knickish: @not-fl3 looks like that was optional, got it down to

> cargo +nightly tree --features "no_std, binary"
```
nanoserde v0.2.0
├── hashbrown v0.14.5
│   └── ahash v0.8.11
│       ├── cfg-if v1.0.0
│       ├── once_cell v1.19.0
│       └── zerocopy v0.7.34
│       [build-dependencies]
│       └── version_check v0.9.4
└── nanoserde-derive v0.2.0 (proc-macro)
```
not-fl3: `--target=all` still got `syn` etc, and Cargo alwas downloads it now, even when `no-std` is off. 
not-fl3: Actually, it was not the case for 0.12 (tho 0.12 use getrandom/wasi...)
Do we really need 0.14? Is it worth downloading 6 extra fairly heavy dependencies? 

`--target=all` do happen on the CI and cargo downloads all those dependencies no matter what features are used, so I would rather not have `hashbrown` from now on at all to be honest, but I am not sure if this would be very easy to do 
knickish: I guess it makes more sense to make a PR to add nanoserde support for their types in `hashbrown` itself at this point. They already have features for `serde` + `rykv`, so seems likely they would accept it
knickish: Alright, ripped it all out now and switched to a BTreeMap for the toml parser internal representation of arrays so that it can be used with no_std
not-fl3: Wow, such a clever solution, a huge fan of this PR! Somehow I totally overlooked that `alloc` got collections! 

still totally usable with no_std and we still have std::HashMap support for jsons with std! 

Looks super good to me!

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 31233b0..ff100ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,8 +27,7 @@ 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"]
+no_std = []
 
 [dependencies]
-hashbrown = { version = "0.12.3", optional = true }
 nanoserde-derive = { path = "derive", version = "=0.2.0", optional = true }
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
index 30ea539..ceff924 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -14,7 +14,4 @@ default = []
 json = []
 binary = []
 ron = []
-no_std = ["dep:hashbrown"]
-
-[dependencies]
-hashbrown = { version = "0.12.3", optional = true }
+no_std = []
diff --git a/src/serde_bin.rs b/src/serde_bin.rs
index 93e0a1c..66c7fc1 100644
--- a/src/serde_bin.rs
+++ b/src/serde_bin.rs
@@ -1,18 +1,11 @@
 use core::convert::TryInto;
-use core::hash::Hash;
 
 use alloc::borrow::ToOwned;
 use alloc::boxed::Box;
-use alloc::collections::{BTreeSet, LinkedList};
+use alloc::collections::{BTreeMap, BTreeSet, LinkedList};
 use alloc::string::String;
 use alloc::vec::Vec;
 
-#[cfg(feature = "no_std")]
-use hashbrown::{HashMap, HashSet};
-
-#[cfg(not(feature = "no_std"))]
-use std::collections::{HashMap, HashSet};
-
 /// A trait for objects that can be serialized to binary.
 pub trait SerBin {
     /// Serialize Self to bytes.
@@ -294,7 +287,8 @@ where
     }
 }
 
-impl<T> SerBin for HashSet<T>
+#[cfg(not(feature = "no_std"))]
+impl<T> SerBin for std::collections::HashSet<T>
 where
     T: SerBin,
 {
@@ -307,13 +301,14 @@ where
     }
 }
 
-impl<T> DeBin for HashSet<T>
+#[cfg(not(feature = "no_std"))]
+impl<T> DeBin for std::collections::HashSet<T>
 where
-    T: DeBin + Hash + Eq,
+    T: DeBin + core::hash::Hash + Eq,
 {
-    fn de_bin(o: &mut usize, d: &[u8]) -> Result<HashSet<T>, DeBinErr> {
+    fn de_bin(o: &mut usize, d: &[u8]) -> Result<Self, DeBinErr> {
         let len: usize = DeBin::de_bin(o, d)?;
-        let mut out = HashSet::with_capacity(len);
+        let mut out = std::collections::HashSet::with_capacity(len);
         for _ in 0..len {
             out.insert(DeBin::de_bin(o, d)?);
         }
@@ -534,7 +529,41 @@ where
     }
 }
 
-impl<K, V> SerBin for HashMap<K, V>
+#[cfg(not(feature = "no_std"))]
+impl<K, V> SerBin for std::collections::HashMap<K, V>
+where
+    K: SerBin,
+    V: SerBin,
+{
+    fn ser_bin(&self, s: &mut Vec<u8>) {
+        let len = self.len();
+        len.ser_bin(s);
+        for (k, v) in self {
+            k.ser_bin(s);
+            v.ser_bin(s);
+        }
+    }
+}
+
+#[cfg(not(feature = "no_std"))]
+impl<K, V> DeBin for std::collections::HashMap<K, V>
+where
+    K: DeBin + core::cmp::Eq + core::hash::Hash,
+    V: DeBin,
+{
+    fn de_bin(o: &mut usize, d: &[u8]) -> Result<Self, DeBinErr> {
+        let len: usize = DeBin::de_bin(o, d)?;
+        let mut h = std::collections::HashMap::with_capacity(len);
+        for _ in 0..len {
+            let k = DeBin::de_bin(o, d)?;
+            let v = DeBin::de_bin(o, d)?;
+            h.insert(k, v);
+        }
+        Ok(h)
+    }
+}
+
+impl<K, V> SerBin for BTreeMap<K, V>
 where
     K: SerBin,
     V: SerBin,
@@ -549,14 +578,14 @@ where
     }
 }
 
-impl<K, V> DeBin for HashMap<K, V>
+impl<K, V> DeBin for BTreeMap<K, V>
 where
-    K: DeBin + core::cmp::Eq + Hash,
+    K: DeBin + core::cmp::Eq + Ord,
     V: DeBin,
 {
     fn de_bin(o: &mut usize, d: &[u8]) -> Result<Self, DeBinErr> {
         let len: usize = DeBin::de_bin(o, d)?;
-        let mut h = HashMap::with_capacity(len);
+        let mut h = BTreeMap::new();
         for _ in 0..len {
             let k = DeBin::de_bin(o, d)?;
             let v = DeBin::de_bin(o, d)?;
diff --git a/src/serde_json.rs b/src/serde_json.rs
index 6f3139e..47ceef0 100644
--- a/src/serde_json.rs
+++ b/src/serde_json.rs
@@ -1,18 +1,11 @@
-use core::hash::Hash;
 use core::str::Chars;
 
 use alloc::boxed::Box;
-use alloc::collections::{BTreeSet, LinkedList};
+use alloc::collections::{BTreeMap, BTreeSet, LinkedList};
 use alloc::format;
 use alloc::string::{String, ToString};
 use alloc::vec::Vec;
 
-#[cfg(feature = "no_std")]
-use hashbrown::{HashMap, HashSet};
-
-#[cfg(not(feature = "no_std"))]
-use std::collections::{HashMap, HashSet};
-
 /// The internal state of a JSON serialization.
 pub struct SerJsonState {
     pub out: String,
@@ -885,7 +878,8 @@ where
     }
 }
 
-impl<T> SerJson for HashSet<T>
+#[cfg(not(feature = "no_std"))]
+impl<T> SerJson for std::collections::HashSet<T>
 where
     T: SerJson,
 {
@@ -905,12 +899,13 @@ where
     }
 }
 
-impl<T> DeJson for HashSet<T>
+#[cfg(not(feature = "no_std"))]
+impl<T> DeJson for std::collections::HashSet<T>
 where
-    T: DeJson + Hash + Eq,
+    T: DeJson + core::hash::Hash + Eq,
 {
-    fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<HashSet<T>, DeJsonErr> {
-        let mut out = HashSet::new();
+    fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<Self, DeJsonErr> {
+        let mut out = std::collections::HashSet::new();
         s.block_open(i)?;
 
         while s.tok != DeJsonTok::BlockClose {
@@ -1177,7 +1172,53 @@ where
     }
 }
 
-impl<K, V> SerJson for HashMap<K, V>
+#[cfg(not(feature = "no_std"))]
+impl<K, V> SerJson for std::collections::HashMap<K, V>
+where
+    K: SerJson,
+    V: SerJson,
+{
+    fn ser_json(&self, d: usize, s: &mut SerJsonState) {
+        s.out.push('{');
+        let len = self.len();
+        let mut index = 0;
+        for (k, v) in self {
+            s.indent(d + 1);
+            k.ser_json(d + 1, s);
+            s.out.push(':');
+            v.ser_json(d + 1, s);
+            if (index + 1) < len {
+                s.conl();
+            }
+            index += 1;
+        }
+        s.indent(d);
+        s.out.push('}');
+    }
+}
+
+#[cfg(not(feature = "no_std"))]
+impl<K, V> DeJson for std::collections::HashMap<K, V>
+where
+    K: DeJson + Eq + core::hash::Hash,
+    V: DeJson,
+{
+    fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<Self, DeJsonErr> {
+        let mut h = std::collections::HashMap::new();
+        s.curly_open(i)?;
+        while s.tok != DeJsonTok::CurlyClose {
+            let k = DeJson::de_json(s, i)?;
+            s.colon(i)?;
+            let v = DeJson::de_json(s, i)?;
+            s.eat_comma_curly(i)?;
+            h.insert(k, v);
+        }
+        s.curly_close(i)?;
+        Ok(h)
+    }
+}
+
+impl<K, V> SerJson for BTreeMap<K, V>
 where
     K: SerJson,
     V: SerJson,
@@ -1201,13 +1242,13 @@ where
     }
 }
 
-impl<K, V> DeJson for HashMap<K, V>
+impl<K, V> DeJson for BTreeMap<K, V>
 where
-    K: DeJson + Eq + Hash,
+    K: DeJson + Eq + Ord,
     V: DeJson,
 {
     fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<Self, DeJsonErr> {
-        let mut h = HashMap::new();
+        let mut h = BTreeMap::new();
         s.curly_open(i)?;
         while s.tok != DeJsonTok::CurlyClose {
             let k = DeJson::de_json(s, i)?;
diff --git a/src/serde_ron.rs b/src/serde_ron.rs
index ba7b049..ce8b954 100644
--- a/src/serde_ron.rs
+++ b/src/serde_ron.rs
@@ -1,18 +1,11 @@
-use core::hash::Hash;
 use core::str::Chars;
 
 use alloc::boxed::Box;
-use alloc::collections::{BTreeSet, LinkedList};
+use alloc::collections::{BTreeMap, BTreeSet, LinkedList};
 use alloc::format;
 use alloc::string::{String, ToString};
 use alloc::vec::Vec;
 
-#[cfg(feature = "no_std")]
-use hashbrown::{HashMap, HashSet};
-
-#[cfg(not(feature = "no_std"))]
-use std::collections::{HashMap, HashSet};
-
 /// The internal state of a RON serialization.
 pub struct SerRonState {
     pub out: String,
@@ -883,7 +876,8 @@ where
     }
 }
 
-impl<T> SerRon for HashSet<T>
+#[cfg(not(feature = "no_std"))]
+impl<T> SerRon for std::collections::HashSet<T>
 where
     T: SerRon,
 {
@@ -903,12 +897,13 @@ where
     }
 }
 
-impl<T> DeRon for HashSet<T>
+#[cfg(not(feature = "no_std"))]
+impl<T> DeRon for std::collections::HashSet<T>
 where
-    T: DeRon + Hash + Eq,
+    T: DeRon + core::hash::Hash + Eq,
 {
-    fn de_ron(s: &mut DeRonState, i: &mut Chars) -> Result<HashSet<T>, DeRonErr> {
-        let mut out = HashSet::new();
+    fn de_ron(s: &mut DeRonState, i: &mut Chars) -> Result<Self, DeRonErr> {
+        let mut out = std::collections::HashSet::new();
         s.block_open(i)?;
 
         while s.tok != DeRonTok::BlockClose {
@@ -1189,7 +1184,48 @@ where
     }
 }
 
-impl<K, V> SerRon for HashMap<K, V>
+#[cfg(not(feature = "no_std"))]
+impl<K, V> SerRon for std::collections::HashMap<K, V>
+where
+    K: SerRon,
+    V: SerRon,
+{
+    fn ser_ron(&self, d: usize, s: &mut SerRonState) {
+        s.out.push_str("{\n");
+        for (k, v) in self {
+            s.indent(d + 1);
+            k.ser_ron(d + 1, s);
+            s.out.push_str(":");
+            v.ser_ron(d + 1, s);
+            s.conl();
+        }
+        s.indent(d);
+        s.out.push('}');
+    }
+}
+
+#[cfg(not(feature = "no_std"))]
+impl<K, V> DeRon for std::collections::HashMap<K, V>
+where
+    K: DeRon + Eq + core::hash::Hash,
+    V: DeRon,
+{
+    fn de_ron(s: &mut DeRonState, i: &mut Chars) -> Result<Self, DeRonErr> {
+        let mut h = std::collections::HashMap::new();
+        s.curly_open(i)?;
+        while s.tok != DeRonTok::CurlyClose {
+            let k = DeRon::de_ron(s, i)?;
+            s.colon(i)?;
+            let v = DeRon::de_ron(s, i)?;
+            s.eat_comma_curly(i)?;
+            h.insert(k, v);
+        }
+        s.curly_close(i)?;
+        Ok(h)
+    }
+}
+
+impl<K, V> SerRon for BTreeMap<K, V>
 where
     K: SerRon,
     V: SerRon,
@@ -1208,13 +1244,13 @@ where
     }
 }
 
-impl<K, V> DeRon for HashMap<K, V>
+impl<K, V> DeRon for BTreeMap<K, V>
 where
-    K: DeRon + Eq + Hash,
+    K: DeRon + Eq + Ord,
     V: DeRon,
 {
     fn de_ron(s: &mut DeRonState, i: &mut Chars) -> Result<Self, DeRonErr> {
-        let mut h = HashMap::new();
+        let mut h = BTreeMap::new();
         s.curly_open(i)?;
         while s.tok != DeRonTok::CurlyClose {
             let k = DeRon::de_ron(s, i)?;
diff --git a/src/toml.rs b/src/toml.rs
index bfe4843..3073e02 100644
--- a/src/toml.rs
+++ b/src/toml.rs
@@ -2,13 +2,7 @@ use core::str::Chars;
 
 use alloc::format;
 use alloc::string::{String, ToString};
-use alloc::{vec, vec::Vec};
-
-#[cfg(feature = "no_std")]
-use hashbrown::HashMap;
-
-#[cfg(not(feature = "no_std"))]
-use std::collections::HashMap;
+use alloc::{collections::BTreeMap, vec, vec::Vec};
 
 /// Pattern matching any valid unquoted key character as u32.
 /// ABNF line: https://github.com/toml-lang/toml/blob/2431aa308a7bc97eeb50673748606e23a6e0f201/toml.abnf#L55
@@ -121,12 +115,12 @@ pub enum Toml {
     Bool(bool),
     Num(f64),
     Date(String),
-    Array(Vec<HashMap<String, Toml>>),
+    Array(Vec<BTreeMap<String, Toml>>),
     SimpleArray(Vec<Toml>),
 }
 
 impl core::ops::Index<usize> for Toml {
-    type Output = HashMap<String, Toml>;
+    type Output = BTreeMap<String, Toml>;
 
     fn index(&self, index: usize) -> &Self::Output {
         match self {
@@ -178,7 +172,7 @@ impl Toml {
     /// Get the TOML value as a table
     ///
     /// Panics if the TOML value isn't actually a table
-    pub fn arr(&self) -> &Vec<HashMap<String, Toml>> {
+    pub fn arr(&self) -> &Vec<BTreeMap<String, Toml>> {
         match self {
             Toml::Array(array) => array,
             _ => panic!(),
@@ -222,7 +216,7 @@ impl core::fmt::Display for TomlErr {
 }
 
 struct Out {
-    out: HashMap<String, Toml>,
+    out: BTreeMap<String, Toml>,
     active_array_element: Option<(String, usize)>,
 }
 impl Out {
@@ -234,7 +228,7 @@ impl Out {
         let n = match self.out.get_mut(key).unwrap() {
             Toml::Array(array) => {
                 let n = array.len();
-                array.push(HashMap::new());
+                array.push(BTreeMap::new());
                 n
             }
             _ => unreachable!(),
@@ -243,7 +237,7 @@ impl Out {
         self.active_array_element = Some((key.to_string(), n));
     }
 
-    fn out(&mut self) -> &mut HashMap<String, Toml> {
+    fn out(&mut self) -> &mut BTreeMap<String, Toml> {
         if let Some((table, n)) = self.active_array_element.clone() {
             match self.out.get_mut(&table).unwrap() {
                 Toml::Array(array) => &mut array[n],
@@ -263,12 +257,12 @@ impl std::error::Error for TomlErr {}
 
 impl TomlParser {
     /// Parse a TOML string.
-    pub fn parse(data: &str) -> Result<HashMap<String, Toml>, TomlErr> {
+    pub fn parse(data: &str) -> Result<BTreeMap<String, Toml>, TomlErr> {
         let i = &mut data.chars();
         let mut t = TomlParser::default();
         t.next(i);
         let mut out = Out {
-            out: HashMap::new(),
+            out: BTreeMap::new(),
             active_array_element: None,
         };
         let mut local_scope = String::new();
@@ -377,7 +371,7 @@ impl TomlParser {
         local_scope: &String,
         key: String,
         i: &mut Chars,
-        out: &mut HashMap<String, Toml>,
+        out: &mut BTreeMap<String, Toml>,
     ) -> Result<(), TomlErr> {
         let tok = self.next_tok(i)?;
         if tok != TomlTok::Equals {


Test patch:
diff --git a/tests/bin.rs b/tests/bin.rs
index 27be065..3a3371d 100644
--- a/tests/bin.rs
+++ b/tests/bin.rs
@@ -1,14 +1,10 @@
 #![cfg(feature = "binary")]
-use std::{
-    array,
-    collections::{BTreeSet, LinkedList},
-    sync::atomic::AtomicBool,
-};
 
-#[cfg(feature = "no_std")]
-use hashbrown::{HashMap, HashSet};
-#[cfg(not(feature = "no_std"))]
-use std::collections::{HashMap, HashSet};
+extern crate alloc;
+
+use std::{array, sync::atomic::AtomicBool};
+
+use alloc::collections::{BTreeMap, BTreeSet, LinkedList};
 
 use nanoserde::{DeBin, SerBin};
 
@@ -39,16 +35,16 @@ fn binary() {
 #[test]
 fn binary_generics() {
     #[derive(DeBin, SerBin, PartialEq)]
-    struct TestGenericsWSkip<A, B, C, DD: Eq + std::hash::Hash, EE>
+    struct TestGenericsWSkip<A, B, C, DD: Eq + Ord, EE>
     where
-        EE: Eq + std::hash::Hash,
+        EE: Eq + Ord,
     {
         test1: A,
         test2: B,
         test3: C,
         #[nserde(skip)]
-        test4: HashMap<DD, A>,
-        test5: HashMap<EE, A>,
+        test4: BTreeMap<DD, A>,
+        test5: BTreeMap<EE, A>,
     }
 
     let test: TestGenericsWSkip<i32, usize, String, Option<bool>, u128> = TestGenericsWSkip {
@@ -255,14 +251,14 @@ fn collections() {
     pub struct Test {
         pub a: Vec<i32>,
         pub b: LinkedList<f32>,
-        pub c: HashSet<i32>,
+        pub c: BTreeMap<i32, i32>,
         pub d: BTreeSet<i32>,
     }
 
     let test: Test = Test {
         a: vec![1, 2, 3],
         b: vec![1.0, 2.0, 3.0, 4.0].into_iter().collect(),
-        c: vec![1, 2, 3, 4, 5].into_iter().collect(),
+        c: vec![(1, 2), (3, 4)].into_iter().collect(),
         d: vec![1, 2, 3, 4, 5, 6].into_iter().collect(),
     };
 
diff --git a/tests/json.rs b/tests/json.rs
index 5275b6f..5aa14d1 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -2,15 +2,12 @@
 use nanoserde::{DeJson, SerJson};
 
 use std::{
-    collections::{BTreeSet, LinkedList},
+    collections::{BTreeMap, BTreeSet, LinkedList},
     sync::atomic::AtomicBool,
 };
 
-#[cfg(feature = "no_std")]
-use hashbrown::{HashMap, HashSet};
-
 #[cfg(not(feature = "no_std"))]
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 
 #[test]
 fn de() {
@@ -403,11 +400,11 @@ fn one_field() {
 fn one_field_map() {
     #[derive(DeJson, SerJson, PartialEq)]
     pub struct OneField {
-        field: HashMap<String, f32>,
+        field: BTreeMap<String, f32>,
     }
 
     let test = OneField {
-        field: HashMap::new(),
+        field: BTreeMap::new(),
     };
     let bytes = SerJson::serialize_json(&test);
     let test_deserialized = DeJson::deserialize_json(&bytes).unwrap();
@@ -470,6 +467,7 @@ fn path_type() {
     assert_eq!(bar.d, Some(vec![vec![1, 2], vec![3, 4]]));
 }
 
+#[cfg(not(feature = "no_std"))]
 #[test]
 fn hashmaps() {
     #[derive(DeJson)]
@@ -533,14 +531,14 @@ fn collections() {
     pub struct Test {
         pub a: Vec<i32>,
         pub b: LinkedList<f32>,
-        pub c: HashSet<i32>,
+        pub c: BTreeMap<i32, i32>,
         pub d: BTreeSet<i32>,
     }
 
     let test: Test = Test {
         a: vec![1, 2, 3],
         b: vec![1.0, 2.0, 3.0, 4.0].into_iter().collect(),
-        c: vec![1, 2, 3, 4, 5].into_iter().collect(),
+        c: vec![(1, 2), (3, 4)].into_iter().collect(),
         d: vec![1, 2, 3, 4, 5, 6].into_iter().collect(),
     };
 
diff --git a/tests/ron.rs b/tests/ron.rs
index 856a978..df8395e 100644
--- a/tests/ron.rs
+++ b/tests/ron.rs
@@ -2,15 +2,12 @@
 use nanoserde::{DeRon, SerRon};
 
 use std::{
-    collections::{BTreeSet, LinkedList},
+    collections::{BTreeMap, BTreeSet, LinkedList},
     sync::atomic::AtomicBool,
 };
 
-#[cfg(feature = "no_std")]
-use hashbrown::{HashMap, HashSet};
-
 #[cfg(not(feature = "no_std"))]
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 
 #[test]
 fn ron_de() {
@@ -175,11 +172,11 @@ fn one_field() {
 fn one_field_map() {
     #[derive(DeRon, SerRon, PartialEq)]
     pub struct OneField {
-        field: HashMap<String, f32>,
+        field: BTreeMap<String, f32>,
     }
 
     let test = OneField {
-        field: HashMap::new(),
+        field: BTreeMap::new(),
     };
     let bytes = SerRon::serialize_ron(&test);
     let test_deserialized = DeRon::deserialize_ron(&bytes).unwrap();
@@ -223,14 +220,14 @@ fn collections() {
     pub struct Test {
         pub a: Vec<i32>,
         pub b: LinkedList<f32>,
-        pub c: HashSet<i32>,
+        pub c: BTreeMap<i32, i32>,
         pub d: BTreeSet<i32>,
     }
 
     let test: Test = Test {
         a: vec![1, 2, 3],
         b: vec![1.0, 2.0, 3.0, 4.0].into_iter().collect(),
-        c: vec![1, 2, 3, 4, 5].into_iter().collect(),
+        c: vec![(1, 2), (3, 4)].into_iter().collect(),
         d: vec![1, 2, 3, 4, 5, 6].into_iter().collect(),
     };
 
@@ -266,6 +263,7 @@ fn path_type() {
     assert_eq!(bar.d, Some(vec![vec![1, 2], vec![3, 4]]));
 }
 
+#[cfg(not(feature = "no_std"))]
 #[test]
 fn hashmaps() {
     #[derive(DeRon)]
diff --git a/tests/ser_de.rs b/tests/ser_de.rs
index 4c37964..0509b0c 100644
--- a/tests/ser_de.rs
+++ b/tests/ser_de.rs
@@ -1,4 +1,7 @@
 #![cfg(any(feature = "binary", feature = "json", feature = "ron"))]
+
+extern crate alloc;
+
 #[cfg(feature = "binary")]
 use nanoserde::{DeBin, SerBin};
 #[cfg(feature = "json")]
@@ -6,11 +9,7 @@ use nanoserde::{DeJson, SerJson};
 #[cfg(feature = "ron")]
 use nanoserde::{DeRon, SerRon};
 
-#[cfg(feature = "no_std")]
-use hashbrown::HashMap;
-
-#[cfg(not(feature = "no_std"))]
-use std::collections::HashMap;
+use alloc::collections::BTreeMap;
 
 #[test]
 fn ser_de() {
@@ -23,12 +22,12 @@ fn ser_de() {
         pub b: f32,
         c: Option<String>,
         d: Option<String>,
-        e: Option<HashMap<String, String>>,
+        e: Option<BTreeMap<String, String>>,
         f: Option<([u32; 4], String)>,
         g: (),
     }
 
-    let mut map = HashMap::new();
+    let mut map = BTreeMap::new();
     map.insert("a".to_string(), "b".to_string());
 
     let test: Test = Test {
diff --git a/tests/toml.rs b/tests/toml.rs
index c7c3c39..05389a3 100644
--- a/tests/toml.rs
+++ b/tests/toml.rs
@@ -1,10 +1,8 @@
 #![cfg(feature = "toml")]
 
-#[cfg(feature = "no_std")]
-use hashbrown::HashMap;
-#[cfg(not(feature = "no_std"))]
-use std::collections::HashMap;
+extern crate alloc;
 
+use alloc::collections::BTreeMap;
 use nanoserde::Toml;
 use nanoserde::TomlParser;
 
@@ -104,7 +102,7 @@ fn toml_key_chars() {
 
     assert_eq!(
         TomlParser::parse(toml_str).unwrap(),
-        HashMap::from([
+        BTreeMap::from([
             (
                 "foo.bar.baz.123abc456def".to_string(),
                 Toml::Str("myval".to_string())