Repository: sharksforarms/deku
Pull request #446 (2024-05-31 22:27:03)

Add bits as an optional feature for bitvec
This is like the final boss of all my performance work. I try to explain the "no warranty and testing are given for the disable of this feature", while still implementing enough to make this useful in the case of "I want perf to matter".

![image](https://github.com/sharksforarms/deku/assets/15236002/5a33fb7a-1087-4b04-a7f8-1b540207ccc6)

bench: https://github.com/wcampbell0x2a/deku-bench/pull/24

github-actions[bot]: ## Benchmark for 4a76c64
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.7±0.04µs | N/A | N/A |
| deku_read_bits | 1320.4±7.58ns | **1261.2±10.48ns** | **-4.48%** |
| deku_read_byte | 3.4±0.22ns | **3.3±0.08ns** | **-2.94%** |
| deku_read_enum | **2.6±0.06ns** | 3.0±0.06ns | **+15.38%** |
| deku_read_vec | 35.5±2.29ns | 35.2±0.54ns | -0.85% |
| deku_write_bits | **149.4±1.91ns** | 168.0±6.70ns | **+12.45%** |
| deku_write_byte | 22.0±0.28ns | 21.9±0.29ns | -0.45% |
| deku_write_enum | 21.4±0.22ns | 21.4±0.46ns | 0.00% |
| deku_write_vec | 387.5±4.44ns | **330.7±5.39ns** | **-14.66%** |
| read_all | 4.4±0.18µs | N/A | N/A |
| read_all_bytes | 3.9±0.03µs | N/A | N/A |

  </details>
  
github-actions[bot]: ## Benchmark for 8947ee1
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.7±0.04µs | N/A | N/A |
| deku_read_bits | 1395.3±21.80ns | **1285.8±42.23ns** | **-7.85%** |
| deku_read_byte | 3.3±0.06ns | 3.3±0.05ns | 0.00% |
| deku_read_enum | **2.6±0.06ns** | 3.0±0.06ns | **+15.38%** |
| deku_read_vec | 35.4±0.51ns | 35.3±0.45ns | -0.28% |
| deku_write_bits | 161.1±5.19ns | **146.4±4.32ns** | **-9.12%** |
| deku_write_byte | 22.1±0.41ns | 21.8±0.39ns | -1.36% |
| deku_write_enum | 21.4±0.23ns | 21.4±0.41ns | 0.00% |
| deku_write_vec | 315.0±6.45ns | 313.0±4.11ns | -0.63% |
| read_all | 4.4±0.06µs | N/A | N/A |
| read_all_bytes | 3.9±0.06µs | N/A | N/A |

  </details>
  
github-actions[bot]: ## Benchmark for 755ed48
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.7±0.03µs | N/A | N/A |
| deku_read_bits | 1375.8±19.60ns | **1303.6±38.98ns** | **-5.25%** |
| deku_read_byte | 3.5±0.06ns | **3.2±0.04ns** | **-8.57%** |
| deku_read_enum | 3.0±0.09ns | **2.6±0.05ns** | **-13.33%** |
| deku_read_vec | 38.1±2.11ns | **35.6±0.40ns** | **-6.56%** |
| deku_write_bits | **160.4±4.86ns** | 175.5±5.04ns | **+9.41%** |
| deku_write_byte | 22.2±0.28ns | **21.8±0.39ns** | **-1.80%** |
| deku_write_enum | 21.4±0.19ns | 21.4±0.45ns | 0.00% |
| deku_write_vec | **313.3±10.86ns** | 412.2±4.90ns | **+31.57%** |
| read_all | 4.2±0.06µs | N/A | N/A |
| read_all_bytes | 5.0±0.04µs | N/A | N/A |

  </details>
  
github-actions[bot]: ## Benchmark for 6bea42b
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.7±0.04µs | 3.7±0.03µs | 0.00% |
| deku_read_bits | 1273.2±21.85ns | 1285.2±21.64ns | +0.94% |
| deku_read_byte | 3.6±0.07ns | **3.2±0.05ns** | **-11.11%** |
| deku_read_enum | 3.0±0.04ns | **2.6±0.06ns** | **-13.33%** |
| deku_read_vec | 35.7±0.53ns | 35.4±0.60ns | -0.84% |
| deku_write_bits | **150.4±2.19ns** | 160.7±6.36ns | **+6.85%** |
| deku_write_byte | 21.9±0.41ns | 22.0±0.22ns | +0.46% |
| deku_write_enum | 21.4±0.19ns | 21.4±0.37ns | 0.00% |
| deku_write_vec | **329.4±4.28ns** | 374.2±4.39ns | **+13.60%** |
| read_all | 4.4±0.04µs | **4.2±0.05µs** | **-4.55%** |
| read_all_bytes | **3.9±0.05µs** | 5.0±0.03µs | **+28.21%** |

  </details>
  
sharksforarms: I think the warning is fair, although, what do we need to do to have more assurance and remove that warning?
wcampbell0x2a: > I think the warning is fair, although, what needs to be done to have more assurance and remove that warning?

I updated this code to behave with running `cargo test --lib --examples --tests --features std --no-default-features`, and added it to CI. This way I am more confident that this options works.
github-actions[bot]: ## Benchmark for c7c2498
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | **3.4±0.03µs** | 3.6±0.07µs | **+5.88%** |
| deku_read_bits | **1119.2±10.17ns** | 1141.7±43.54ns | **+2.01%** |
| deku_read_byte | 3.6±0.07ns | **3.4±0.10ns** | **-5.56%** |
| deku_read_enum | 3.7±0.60ns | **3.1±0.05ns** | **-16.22%** |
| deku_read_vec | 31.7±0.19ns | **30.3±0.48ns** | **-4.42%** |
| deku_write_bits | 185.0±3.43ns | **174.4±3.74ns** | **-5.73%** |
| deku_write_byte | 24.6±0.33ns | **24.1±0.47ns** | **-2.03%** |
| deku_write_enum | 22.6±0.25ns | 22.7±0.38ns | +0.44% |
| deku_write_vec | **411.1±5.49ns** | 434.7±11.80ns | **+5.74%** |
| read_all | **3.8±0.07µs** | 5.5±0.04µs | **+44.74%** |
| read_all_bytes | 5.5±0.01µs | **3.8±0.06µs** | **-30.91%** |

  </details>
  
github-actions[bot]: ## Benchmark for c7c2498
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.6±0.02µs | 3.6±0.01µs | 0.00% |
| deku_read_bits | 1139.0±18.04ns | 1137.2±8.55ns | -0.16% |
| deku_read_byte | 3.4±0.04ns | 3.4±0.07ns | 0.00% |
| deku_read_enum | 3.1±0.05ns | 3.1±0.04ns | 0.00% |
| deku_read_vec | 32.1±0.29ns | **31.1±0.45ns** | **-3.12%** |
| deku_write_bits | **182.8±2.77ns** | 186.8±4.35ns | **+2.19%** |
| deku_write_byte | 24.5±0.20ns | 24.6±0.47ns | +0.41% |
| deku_write_enum | 22.7±0.39ns | 22.6±0.29ns | -0.44% |
| deku_write_vec | 442.2±6.51ns | **427.6±5.69ns** | **-3.30%** |
| read_all | **5.5±0.03µs** | 5.6±0.07µs | **+1.82%** |
| read_all_bytes | 3.8±0.04µs | 3.8±0.05µs | 0.00% |

  </details>
  
github-actions[bot]: ## Benchmark for 8082ea9
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.6±0.02µs | **3.4±0.02µs** | **-5.56%** |
| deku_read_bits | 1133.0±6.02ns | **1112.8±16.10ns** | **-1.78%** |
| deku_read_byte | **3.4±0.08ns** | 3.6±0.04ns | **+5.88%** |
| deku_read_enum | **3.2±0.09ns** | 3.5±0.03ns | **+9.38%** |
| deku_read_vec | 32.5±0.43ns | **31.6±0.33ns** | **-2.77%** |
| deku_write_bits | 199.3±3.34ns | **183.7±2.91ns** | **-7.83%** |
| deku_write_byte | 25.0±0.46ns | **24.1±0.35ns** | **-3.60%** |
| deku_write_enum | 22.8±0.33ns | **22.6±0.18ns** | **-0.88%** |
| deku_write_vec | 438.2±6.19ns | **409.3±4.81ns** | **-6.60%** |
| read_all | 5.6±0.02µs | **3.8±0.05µs** | **-32.14%** |
| read_all_bytes | **3.8±0.06µs** | 5.6±0.06µs | **+47.37%** |

  </details>
  
github-actions[bot]: ## Benchmark for 8082ea9
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | **3.4±0.02µs** | 3.6±0.02µs | **+5.88%** |
| deku_read_bits | 1139.9±12.96ns | 1145.6±13.99ns | +0.50% |
| deku_read_byte | 3.6±0.07ns | **3.5±0.29ns** | **-2.78%** |
| deku_read_enum | 3.5±0.04ns | **3.1±0.10ns** | **-11.43%** |
| deku_read_vec | 31.8±0.45ns | **30.8±0.60ns** | **-3.14%** |
| deku_write_bits | 197.7±4.11ns | 195.6±3.56ns | -1.06% |
| deku_write_byte | 24.5±0.33ns | 24.6±0.32ns | +0.41% |
| deku_write_enum | **22.6±0.34ns** | 23.3±0.29ns | **+3.10%** |
| deku_write_vec | **415.1±5.81ns** | 434.1±4.64ns | **+4.58%** |
| read_all | **3.9±0.05µs** | 5.5±0.03µs | **+41.03%** |
| read_all_bytes | 5.6±0.04µs | **3.9±0.06µs** | **-30.36%** |

  </details>
  
github-actions[bot]: ## Benchmark for 91baeb0
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | **3.6±0.01µs** | 3.7±0.02µs | **+2.78%** |
| deku_read_bits | 1180.1±16.54ns | **1133.3±21.38ns** | **-3.97%** |
| deku_read_byte | 3.4±0.12ns | 3.4±0.11ns | 0.00% |
| deku_read_enum | 3.1±0.04ns | 3.1±0.06ns | 0.00% |
| deku_read_vec | **30.4±0.60ns** | 31.8±1.03ns | **+4.61%** |
| deku_write_bits | **188.1±2.74ns** | 213.7±8.74ns | **+13.61%** |
| deku_write_byte | 24.7±0.91ns | **24.1±0.48ns** | **-2.43%** |
| deku_write_enum | 22.7±0.39ns | 22.6±0.35ns | -0.44% |
| deku_write_vec | 413.6±10.55ns | 413.1±8.07ns | -0.12% |
| read_all | 5.6±0.03µs | **5.5±0.05µs** | **-1.79%** |
| read_all_bytes | 3.8±0.12µs | 3.8±0.10µs | 0.00% |

  </details>
  
github-actions[bot]: ## Benchmark for 91baeb0
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.6±0.13µs | 3.6±0.02µs | 0.00% |
| deku_read_bits | 1158.1±14.06ns | 1165.7±29.54ns | +0.66% |
| deku_read_byte | 3.4±0.13ns | 3.4±0.08ns | 0.00% |
| deku_read_enum | 3.1±0.04ns | 3.1±0.02ns | 0.00% |
| deku_read_vec | 31.0±0.70ns | 31.1±0.45ns | +0.32% |
| deku_write_bits | 191.5±3.97ns | 195.3±10.10ns | +1.98% |
| deku_write_byte | 24.6±0.32ns | 24.8±1.40ns | +0.81% |
| deku_write_enum | 22.8±1.01ns | 22.7±0.33ns | -0.44% |
| deku_write_vec | 612.3±4.22ns | **412.5±6.40ns** | **-32.63%** |
| read_all | 5.5±0.03µs | **3.8±0.06µs** | **-30.91%** |
| read_all_bytes | **3.8±0.06µs** | 5.5±0.04µs | **+44.74%** |

  </details>
  
github-actions[bot]: ## Benchmark for 2f63882
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.6±0.01µs | 3.6±0.02µs | 0.00% |
| deku_read_bits | 1138.6±18.05ns | 1132.1±17.82ns | -0.57% |
| deku_read_byte | **3.4±0.05ns** | 3.5±0.13ns | **+2.94%** |
| deku_read_enum | 3.1±0.02ns | 3.1±0.05ns | 0.00% |
| deku_read_vec | **31.7±0.48ns** | 34.4±1.68ns | **+8.52%** |
| deku_write_bits | 205.6±17.15ns | **190.6±4.81ns** | **-7.30%** |
| deku_write_byte | **24.6±0.19ns** | 25.4±1.67ns | **+3.25%** |
| deku_write_enum | 22.7±0.42ns | 22.8±0.38ns | +0.44% |
| deku_write_vec | 418.6±6.53ns | 420.3±6.71ns | +0.41% |
| read_all | 5.5±0.02µs | **3.8±0.06µs** | **-30.91%** |
| read_all_bytes | **3.8±0.08µs** | 5.5±0.03µs | **+44.74%** |

  </details>
  
github-actions[bot]: ## Benchmark for f998f16
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 3.4±0.02µs | 3.4±0.02µs | 0.00% |
| deku_read_bits | 1141.2±15.34ns | **1105.0±25.97ns** | **-3.17%** |
| deku_read_byte | 3.6±0.15ns | 3.6±0.08ns | 0.00% |
| deku_read_enum | 3.5±0.04ns | 3.5±0.06ns | 0.00% |
| deku_read_vec | 31.7±0.51ns | 31.7±0.47ns | 0.00% |
| deku_write_bits | 196.0±4.13ns | **179.3±4.26ns** | **-8.52%** |
| deku_write_byte | 24.6±0.52ns | 24.2±0.41ns | -1.63% |
| deku_write_enum | 22.6±0.18ns | 22.7±0.38ns | +0.44% |
| deku_write_vec | **411.5±6.98ns** | 435.4±10.70ns | **+5.81%** |
| read_all | 3.8±0.05µs | 3.8±0.04µs | 0.00% |
| read_all_bytes | 5.6±0.03µs | **5.5±0.08µs** | **-1.79%** |

  </details>
  
github-actions[bot]: ## Benchmark for 45a4aea
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.01µs | 5.0±0.01µs | 0.00% |
| deku_read_bits | 1160.9±14.17ns | **1140.3±20.76ns** | **-1.77%** |
| deku_read_byte | 3.3±0.03ns | **3.1±0.07ns** | **-6.06%** |
| deku_read_enum | 3.2±0.08ns | 3.2±0.03ns | 0.00% |
| deku_read_vec | 42.3±0.60ns | 42.3±0.36ns | 0.00% |
| deku_write_bits | **185.0±4.46ns** | 195.7±5.50ns | **+5.78%** |
| deku_write_byte | 24.9±0.41ns | **24.4±0.51ns** | **-2.01%** |
| deku_write_enum | 23.5±0.33ns | **23.0±0.62ns** | **-2.13%** |
| deku_write_vec | 432.3±6.61ns | **410.5±5.74ns** | **-5.04%** |
| read_all | 5.5±0.02µs | 5.5±0.02µs | 0.00% |
| read_all_bytes | 3.8±0.03µs | 3.8±0.08µs | 0.00% |

  </details>
  
github-actions[bot]: ## Benchmark for 07f5db4
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.01µs | 5.0±0.14µs | 0.00% |
| deku_read_bits | 1141.9±17.54ns | **1122.1±10.32ns** | **-1.73%** |
| deku_read_byte | 3.3±0.07ns | **3.1±0.05ns** | **-6.06%** |
| deku_read_enum | 3.2±0.03ns | 3.2±0.04ns | 0.00% |
| deku_read_vec | 42.5±0.21ns | 42.5±0.14ns | 0.00% |
| deku_write_bits | **184.3±3.97ns** | 196.0±3.19ns | **+6.35%** |
| deku_write_byte | 24.8±0.25ns | **24.4±0.35ns** | **-1.61%** |
| deku_write_enum | 23.5±0.50ns | **23.0±0.36ns** | **-2.13%** |
| deku_write_vec | 437.7±4.66ns | **419.1±3.82ns** | **-4.25%** |
| read_all | 5.5±0.01µs | 5.5±0.03µs | 0.00% |
| read_all_bytes | 3.9±0.04µs | **3.8±0.07µs** | **-2.56%** |

  </details>
  

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 4e3fd01..81684c5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -22,6 +22,8 @@ jobs:
       - run: cargo test --all
       # run examples
       - run: cargo run --example 2>&1 | grep -P '   ' | awk '{print $1}' | xargs -i cargo run --example {}
+      # test with no bits feature (don't test docs)
+      - run: cargo test --lib --examples --tests --features std --no-default-features
 
   # Only build on MSRV, since trybuild will fail on older version
   build-msrv:
diff --git a/Cargo.toml b/Cargo.toml
index 4d3ad5f..9a87151 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,16 +20,17 @@ members = [
 ]
 
 [features]
-default = ["std"]
-std = ["deku_derive/std", "bitvec/std", "alloc", "no_std_io/std"]
-alloc = ["bitvec/alloc"]
+default = ["std", "bits"]
+std = ["deku_derive/std", "bitvec?/std", "alloc", "no_std_io/std"]
+alloc = ["bitvec?/alloc"]
 logging = ["deku_derive/logging", "log"]
 no-assert-string = ["deku_derive/no-assert-string"]
 error_in_core = []
+bits = ["dep:bitvec", "deku_derive/bits"]
 
 [dependencies]
 deku_derive = { version = "^0.17.0", path = "deku-derive", default-features = false}
-bitvec = { version = "1.0.1", default-features = false }
+bitvec = { version = "1.0.1", default-features = false, optional = true }
 log = { version = "0.4.21", optional = true }
 no_std_io = { version = "0.8.0", default-features = false, features = ["alloc"], package = "no_std_io2" }
 rustversion = "1.0.16"
@@ -55,3 +56,31 @@ workspace = true
 # Triggers in macro generated code of darling
 # https://github.com/rust-lang/rust-clippy/issues/12643
 manual-unwrap-or-default = "allow"
+
+[[example]]
+name = "custom_reader_and_writer"
+required-features = ["bits"]
+
+[[example]]
+name = "deku_input"
+
+[[example]]
+name = "enums_catch_all"
+required-features = ["bits"]
+
+[[example]]
+name = "enums"
+
+[[example]]
+name = "example"
+required-features = ["bits"]
+
+[[example]]
+name = "ipv4"
+required-features = ["bits"]
+
+[[example]]
+name = "many"
+
+[[example]]
+name = "read_all"
diff --git a/benches/deku.rs b/benches/deku.rs
index d6e086c..7951433 100644
--- a/benches/deku.rs
+++ b/benches/deku.rs
@@ -3,6 +3,7 @@ use std::io::{Cursor, Read, Seek};
 use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
 use deku::prelude::*;
 
+#[cfg(feature = "bits")]
 #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
 struct DekuBits {
     #[deku(bits = 1)]
@@ -61,6 +62,7 @@ fn criterion_benchmark(c: &mut Criterion) {
             }))
         })
     });
+    #[cfg(feature = "bits")]
     c.bench_function("deku_read_bits", |b| {
         let reader = Cursor::new(&[0x01; 1]);
         b.iter_batched(
@@ -69,6 +71,7 @@ fn criterion_benchmark(c: &mut Criterion) {
             BatchSize::SmallInput,
         )
     });
+    #[cfg(feature = "bits")]
     c.bench_function("deku_write_bits", |b| {
         b.iter(|| {
             deku_write(black_box(&DekuBits {
diff --git a/deku-derive/Cargo.toml b/deku-derive/Cargo.toml
index b7c3d3a..02d7b70 100644
--- a/deku-derive/Cargo.toml
+++ b/deku-derive/Cargo.toml
@@ -16,6 +16,7 @@ proc-macro = true
 std = ["proc-macro-crate"]
 logging = []
 no-assert-string = []
+bits = []
 
 [dependencies]
 quote = "1.0"
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index a686b3b..4c34673 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -136,6 +136,7 @@ struct DekuData {
     id_type: Option<TokenStream>,
 
     /// enum only: bit size of the enum `id`
+    #[cfg(feature = "bits")]
     bits: Option<Num>,
 
     /// enum only: byte size of the enum `id`
@@ -198,6 +199,7 @@ impl DekuData {
             magic: receiver.magic,
             id: receiver.id,
             id_type: receiver.id_type?,
+            #[cfg(feature = "bits")]
             bits: receiver.bits,
             bytes: receiver.bytes,
             seek_rewind: receiver.seek_rewind,
@@ -224,7 +226,7 @@ impl DekuData {
         match data.data {
             ast::Data::Struct(_) => {
                 // Validate id_* attributes are being used on an enum
-                if data.id_type.is_some() {
+                let ret = if data.id_type.is_some() {
                     Err(cerror(
                         data.id_type.span(),
                         "`id_type` only supported on enum",
@@ -233,11 +235,16 @@ impl DekuData {
                     Err(cerror(data.id.span(), "`id` only supported on enum"))
                 } else if data.bytes.is_some() {
                     Err(cerror(data.bytes.span(), "`bytes` only supported on enum"))
-                } else if data.bits.is_some() {
-                    Err(cerror(data.bits.span(), "`bits` only supported on enum"))
                 } else {
                     Ok(())
+                };
+
+                #[cfg(feature = "bits")]
+                if ret.is_ok() && data.bits.is_some() {
+                    return Err(cerror(data.bits.span(), "`bits` only supported on enum"));
                 }
+
+                ret
             }
             ast::Data::Enum(_) => {
                 // Validate `id_type` or `id` is specified
@@ -257,6 +264,7 @@ impl DekuData {
                 }
 
                 // Validate `id_*` used correctly
+                #[cfg(feature = "bits")]
                 if data.id.is_some() && data.bits.is_some() {
                     return Err(cerror(
                         data.ident.span(),
@@ -271,6 +279,7 @@ impl DekuData {
                 }
 
                 // Validate either `bits` or `bytes` is specified
+                #[cfg(feature = "bits")]
                 if data.bits.is_some() && data.bytes.is_some() {
                     return Err(cerror(
                         data.bits.span(),
@@ -336,7 +345,10 @@ impl<'a> TryFrom<&'a DekuData> for DekuDataEnum<'a> {
 
         let id_args = crate::macros::gen_id_args(
             deku_data.endian.as_ref(),
+            #[cfg(feature = "bits")]
             deku_data.bits.as_ref(),
+            #[cfg(not(feature = "bits"))]
+            None,
             deku_data.bytes.as_ref(),
         )?;
 
@@ -393,6 +405,7 @@ struct FieldData {
     endian: Option<syn::LitStr>,
 
     /// field bit size
+    #[cfg(feature = "bits")]
     bits: Option<Num>,
 
     /// field byte size
@@ -402,6 +415,7 @@ struct FieldData {
     count: Option<TokenStream>,
 
     /// tokens providing the number of bits for the length of the container
+    #[cfg(feature = "bits")]
     bits_read: Option<TokenStream>,
 
     /// tokens providing the number of bytes for the length of the container
@@ -432,12 +446,14 @@ struct FieldData {
     skip: bool,
 
     /// pad a number of bits before
+    #[cfg(feature = "bits")]
     pad_bits_before: Option<TokenStream>,
 
     /// pad a number of bytes before
     pad_bytes_before: Option<TokenStream>,
 
     /// pad a number of bits after
+    #[cfg(feature = "bits")]
     pad_bits_after: Option<TokenStream>,
 
     /// pad a number of bytes after
@@ -486,9 +502,11 @@ impl FieldData {
             ident: receiver.ident,
             ty: receiver.ty,
             endian: receiver.endian,
+            #[cfg(feature = "bits")]
             bits: receiver.bits,
             bytes: receiver.bytes,
             count: receiver.count?,
+            #[cfg(feature = "bits")]
             bits_read: receiver.bits_read?,
             bytes_read: receiver.bytes_read?,
             until: receiver.until?,
@@ -499,8 +517,10 @@ impl FieldData {
             reader: receiver.reader?,
             writer: receiver.writer?,
             skip: receiver.skip,
+            #[cfg(feature = "bits")]
             pad_bits_before: receiver.pad_bits_before?,
             pad_bytes_before: receiver.pad_bytes_before?,
+            #[cfg(feature = "bits")]
             pad_bits_after: receiver.pad_bits_after?,
             pad_bytes_after: receiver.pad_bytes_after?,
             temp: receiver.temp,
@@ -524,6 +544,7 @@ impl FieldData {
 
     fn validate(data: &FieldData) -> Result<(), TokenStream> {
         // Validate either `read_bytes` or `read_bits` is specified
+        #[cfg(feature = "bits")]
         if data.bits_read.is_some() && data.bytes_read.is_some() {
             return Err(cerror(
                 data.bits_read.span(),
@@ -532,6 +553,7 @@ impl FieldData {
         }
 
         // Validate either `count` or `bits_read`/`bytes_read` is specified
+        #[cfg(feature = "bits")]
         if data.count.is_some() && (data.bits_read.is_some() || data.bytes_read.is_some()) {
             if data.bits_read.is_some() {
                 return Err(cerror(
@@ -546,7 +568,16 @@ impl FieldData {
             }
         }
 
+        #[cfg(not(feature = "bits"))]
+        if data.count.is_some() && data.bytes_read.is_some() {
+            return Err(cerror(
+                data.count.span(),
+                "conflicting: both `count` and `bytes_read` specified on field",
+            ));
+        }
+
         // Validate either `bits` or `bytes` is specified
+        #[cfg(feature = "bits")]
         if data.bits.is_some() && data.bytes.is_some() {
             // FIXME: Use `Span::join` once out of nightly
             return Err(cerror(
@@ -565,6 +596,7 @@ impl FieldData {
         }
 
         // Validate usage of read_all
+        #[cfg(feature = "bits")]
         if data.read_all
             && (data.until.is_some()
                 || data.count.is_some()
@@ -707,6 +739,7 @@ struct DekuReceiver {
     id_type: Result<Option<TokenStream>, ReplacementError>,
 
     /// enum only: bit size of the enum `id`
+    #[cfg(feature = "bits")]
     #[darling(default)]
     bits: Option<Num>,
 
@@ -816,6 +849,7 @@ struct DekuFieldReceiver {
     endian: Option<syn::LitStr>,
 
     /// field bit size
+    #[cfg(feature = "bits")]
     #[darling(default)]
     bits: Option<Num>,
 
@@ -828,6 +862,7 @@ struct DekuFieldReceiver {
     count: Result<Option<TokenStream>, ReplacementError>,
 
     /// tokens providing the number of bits for the length of the container
+    #[cfg(feature = "bits")]
     #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
     bits_read: Result<Option<TokenStream>, ReplacementError>,
 
@@ -871,6 +906,7 @@ struct DekuFieldReceiver {
     skip: bool,
 
     /// pad a number of bits before
+    #[cfg(feature = "bits")]
     #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
     pad_bits_before: Result<Option<TokenStream>, ReplacementError>,
 
@@ -879,6 +915,7 @@ struct DekuFieldReceiver {
     pad_bytes_before: Result<Option<TokenStream>, ReplacementError>,
 
     /// pad a number of bits after
+    #[cfg(feature = "bits")]
     #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
     pad_bits_after: Result<Option<TokenStream>, ReplacementError>,
 
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 587c05a..130aaf9 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -7,7 +7,7 @@ use quote::quote;
 
 use crate::macros::{
     gen_ctx_types_and_arg, gen_field_args, gen_internal_field_ident, gen_internal_field_idents,
-    gen_type_from_ctx_id, pad_bits, token_contains_string, wrap_default_ctx,
+    gen_type_from_ctx_id, token_contains_string, wrap_default_ctx,
 };
 use crate::{DekuData, DekuDataEnum, DekuDataStruct, FieldData, Id};
 
@@ -558,6 +558,7 @@ fn emit_bit_byte_offsets(
     (bit_offset, byte_offset)
 }
 
+#[cfg(feature = "bits")]
 fn emit_padding(bit_size: &TokenStream) -> TokenStream {
     let crate_ = super::get_crate_name();
     quote! {
@@ -585,6 +586,29 @@ fn emit_padding(bit_size: &TokenStream) -> TokenStream {
     }
 }
 
+// TODO: if this is a simple calculation such as "8 + 2", this could be const
+#[cfg(not(feature = "bits"))]
+fn emit_padding_bytes(bit_size: &TokenStream) -> TokenStream {
+    let crate_ = super::get_crate_name();
+    quote! {
+        {
+            use core::convert::TryFrom;
+            extern crate alloc;
+            use alloc::borrow::Cow;
+            let __deku_pad = usize::try_from(#bit_size).map_err(|e|
+                ::#crate_::DekuError::InvalidParam(Cow::from(format!(
+                    "Invalid padding param \"({})\": cannot convert to usize",
+                    stringify!(#bit_size)
+                )))
+            )?;
+
+
+            let mut buf = vec![0; __deku_pad];
+            let _ = __deku_reader.read_bytes(__deku_pad, &mut buf)?;
+        }
+    }
+}
+
 fn emit_field_read(
     input: &DekuData,
     i: usize,
@@ -602,6 +626,7 @@ fn emit_field_read(
     // fields to check usage of bit/byte offset
     let field_check_vars = [
         &f.count,
+        #[cfg(feature = "bits")]
         &f.bits_read,
         &f.bytes_read,
         &f.until,
@@ -705,7 +730,10 @@ fn emit_field_read(
     } else {
         let read_args = gen_field_args(
             field_endian,
+            #[cfg(feature = "bits")]
             f.bits.as_ref(),
+            #[cfg(not(feature = "bits"))]
+            None,
             f.bytes.as_ref(),
             f.ctx.as_ref(),
         )?;
@@ -751,17 +779,6 @@ fn emit_field_read(
                     )?
                 }
             }
-        } else if let Some(field_bits) = &f.bits_read {
-            quote! {
-                {
-                    use core::borrow::Borrow;
-                    #type_as_deku_read::from_reader_with_ctx
-                    (
-                        __deku_reader,
-                        (::#crate_::ctx::Limit::new_bit_size(::#crate_::ctx::BitSize(usize::try_from(*((#field_bits).borrow()))?)), (#read_args))
-                    )?
-                }
-            }
         } else if let Some(field_bytes) = &f.bytes_read {
             quote! {
                 {
@@ -795,27 +812,54 @@ fn emit_field_read(
                 }
             }
         } else {
-            quote! {
-                #type_as_deku_read::from_reader_with_ctx
-                (
-                    __deku_reader,
-                    (#read_args)
-                )?
+            let mut ret = quote! {};
+
+            #[cfg(feature = "bits")]
+            if let Some(field_bits) = &f.bits_read {
+                ret.extend(quote! {
+                    {
+                        use core::borrow::Borrow;
+                        #type_as_deku_read::from_reader_with_ctx
+                        (
+                            __deku_reader,
+                            (::#crate_::ctx::Limit::new_bit_size(::#crate_::ctx::BitSize(usize::try_from(*((#field_bits).borrow()))?)), (#read_args))
+                        )?
+                    }
+                })
+            }
+            if ret.is_empty() {
+                ret.extend(quote! {
+                    #type_as_deku_read::from_reader_with_ctx
+                    (
+                        __deku_reader,
+                        (#read_args)
+                    )?
+                })
             }
+
+            ret
         }
     };
 
-    let pad_bits_before = pad_bits(
+    #[cfg(feature = "bits")]
+    let pad_bits_before = crate::macros::pad_bits(
         f.pad_bits_before.as_ref(),
         f.pad_bytes_before.as_ref(),
         emit_padding,
     );
-    let pad_bits_after = pad_bits(
+    #[cfg(feature = "bits")]
+    let pad_bits_after = crate::macros::pad_bits(
         f.pad_bits_after.as_ref(),
         f.pad_bytes_after.as_ref(),
         emit_padding,
     );
 
+    #[cfg(not(feature = "bits"))]
+    let pad_bits_before = crate::macros::pad_bytes(f.pad_bytes_before.as_ref(), emit_padding_bytes);
+
+    #[cfg(not(feature = "bits"))]
+    let pad_bits_after = crate::macros::pad_bytes(f.pad_bytes_after.as_ref(), emit_padding_bytes);
+
     let field_read_normal = quote! {
         let __deku_value = #field_read_func;
         let __deku_value: #field_type = #field_map(__deku_value)?;
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index 4ca8d0d..c0183ee 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -5,7 +5,7 @@ use proc_macro2::TokenStream;
 use quote::quote;
 
 use crate::macros::{
-    assertion_failed, gen_ctx_types_and_arg, gen_field_args, gen_struct_destruction, pad_bits,
+    assertion_failed, gen_ctx_types_and_arg, gen_field_args, gen_struct_destruction,
     token_contains_string, wrap_default_ctx,
 };
 use crate::{DekuData, DekuDataEnum, DekuDataStruct, FieldData, Id};
@@ -91,6 +91,7 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     // Implement `DekuContainerWrite` for types that don't need a context
     if input.ctx.is_none() || (input.ctx.is_some() && input.ctx_default.is_some()) {
+        #[cfg(feature = "bits")]
         tokens.extend(quote! {
              impl #imp core::convert::TryFrom<#ident> for ::#crate_::bitvec::BitVec<u8, ::#crate_::bitvec::Msb0> #wher {
                 type Error = ::#crate_::DekuError;
@@ -100,7 +101,9 @@ fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
                     input.to_bits()
                 }
             }
+        });
 
+        tokens.extend(quote! {
             impl #imp core::convert::TryFrom<#ident> for Vec<u8> #wher {
                 type Error = ::#crate_::DekuError;
 
@@ -288,6 +291,7 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
 
     // Implement `DekuContainerWrite` for types that don't need a context
     if input.ctx.is_none() || (input.ctx.is_some() && input.ctx_default.is_some()) {
+        #[cfg(feature = "bits")]
         tokens.extend(quote! {
              impl #imp core::convert::TryFrom<#ident> for ::#crate_::bitvec::BitVec<u8, ::#crate_::bitvec::Msb0> #wher {
                 type Error = ::#crate_::DekuError;
@@ -297,7 +301,9 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
                     input.to_bits()
                 }
             }
+        });
 
+        tokens.extend(quote! {
             impl #imp core::convert::TryFrom<#ident> for Vec<u8> #wher {
                 type Error = ::#crate_::DekuError;
 
@@ -308,7 +314,7 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
             }
 
             impl #imp DekuContainerWrite for #ident #wher {}
-        })
+        });
     }
 
     let (ctx_types, ctx_arg) = gen_ctx_types_and_arg(input.ctx.as_ref())?;
@@ -454,6 +460,7 @@ fn emit_bit_byte_offsets(
     (bit_offset, byte_offset)
 }
 
+#[cfg(feature = "bits")]
 fn emit_padding(bit_size: &TokenStream) -> TokenStream {
     let crate_ = super::get_crate_name();
     quote! {
@@ -472,6 +479,26 @@ fn emit_padding(bit_size: &TokenStream) -> TokenStream {
     }
 }
 
+// TODO: if this is a simple calculation such as "8 + 2", this could be const
+#[cfg(not(feature = "bits"))]
+fn emit_padding_bytes(bit_size: &TokenStream) -> TokenStream {
+    let crate_ = super::get_crate_name();
+    quote! {
+        {
+            use core::convert::TryFrom;
+            extern crate alloc;
+            use alloc::borrow::Cow;
+            let __deku_pad = usize::try_from(#bit_size).map_err(|e|
+                ::#crate_::DekuError::InvalidParam(Cow::from(format!(
+                    "Invalid padding param \"({})\": cannot convert to usize",
+                    stringify!(#bit_size)
+                )))
+            )?;
+            __deku_writer.write_bytes(&vec![0; __deku_pad])?;
+        }
+    }
+}
+
 fn emit_field_write(
     input: &DekuData,
     i: usize,
@@ -576,7 +603,10 @@ fn emit_field_write(
     } else {
         let write_args = gen_field_args(
             field_endian,
+            #[cfg(feature = "bits")]
             f.bits.as_ref(),
+            #[cfg(not(feature = "bits"))]
+            None,
             f.bytes.as_ref(),
             f.ctx.as_ref(),
         )?;
@@ -596,17 +626,25 @@ fn emit_field_write(
         }
     };
 
-    let pad_bits_before = pad_bits(
+    #[cfg(feature = "bits")]
+    let pad_bits_before = crate::macros::pad_bits(
         f.pad_bits_before.as_ref(),
         f.pad_bytes_before.as_ref(),
         emit_padding,
     );
-    let pad_bits_after = pad_bits(
+    #[cfg(feature = "bits")]
+    let pad_bits_after = crate::macros::pad_bits(
         f.pad_bits_after.as_ref(),
         f.pad_bytes_after.as_ref(),
         emit_padding,
     );
 
+    #[cfg(not(feature = "bits"))]
+    let pad_bits_before = crate::macros::pad_bytes(f.pad_bytes_before.as_ref(), emit_padding_bytes);
+
+    #[cfg(not(feature = "bits"))]
+    let pad_bits_after = crate::macros::pad_bytes(f.pad_bytes_after.as_ref(), emit_padding_bytes);
+
     let field_write_normal = quote! {
         #field_write_func ?;
     };
diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs
index 847db15..19555dd 100644
--- a/deku-derive/src/macros/mod.rs
+++ b/deku-derive/src/macros/mod.rs
@@ -340,6 +340,7 @@ fn token_contains_string(tok: &Option<TokenStream>, s: &str) -> bool {
         .unwrap_or(false)
 }
 
+#[cfg(feature = "bits")]
 fn pad_bits(
     bits: Option<&TokenStream>,
     bytes: Option<&TokenStream>,
@@ -355,6 +356,17 @@ fn pad_bits(
     }
 }
 
+#[cfg(not(feature = "bits"))]
+fn pad_bytes(
+    bytes: Option<&TokenStream>,
+    emit_padding: fn(&TokenStream) -> TokenStream,
+) -> TokenStream {
+    match bytes {
+        Some(pad_bytes) => emit_padding(&quote! {((#pad_bytes))}),
+        None => quote!(),
+    }
+}
+
 /// assertion is false, raise error
 fn assertion_failed(
     v: &TokenStream,
diff --git a/ensure_no_std/Cargo.toml b/ensure_no_std/Cargo.toml
index f5d70c5..e6c88a0 100644
--- a/ensure_no_std/Cargo.toml
+++ b/ensure_no_std/Cargo.toml
@@ -20,6 +20,6 @@ alloc = []
 
 [dependencies]
 cortex-m-rt = "0.7.3"
-deku = { path = "../", default-features = false, features = ["alloc"] }
+deku = { path = "../", default-features = false, features = ["alloc", "bits"] }
 embedded-alloc = "0.5.1"
 
diff --git a/src/impls/bool.rs b/src/impls/bool.rs
index e8f2743..f0bda1f 100644
--- a/src/impls/bool.rs
+++ b/src/impls/bool.rs
@@ -73,6 +73,7 @@ mod tests {
         assert_eq!(expected, res_read);
     }
 
+    #[cfg(feature = "bits")]
     #[test]
     fn test_bool_with_context() {
         let input = &[0b01_000000];
@@ -83,12 +84,16 @@ mod tests {
         assert!(res_read);
     }
 
+    #[cfg(feature = "bits")]
     #[test]
-    fn test_writer() {
+    fn test_writer_bits() {
         let mut writer = Writer::new(Cursor::new(vec![]));
         true.to_writer(&mut writer, BitSize(1)).unwrap();
         assert_eq!(vec![true], writer.rest());
+    }
 
+    #[test]
+    fn test_writer() {
         let mut writer = Writer::new(Cursor::new(vec![]));
         true.to_writer(&mut writer, ()).unwrap();
         assert_eq!(vec![1], writer.inner.into_inner());
diff --git a/src/impls/boxed.rs b/src/impls/boxed.rs
index 46011b0..af3af82 100644
--- a/src/impls/boxed.rs
+++ b/src/impls/boxed.rs
@@ -81,6 +81,7 @@ mod tests {
     use crate::ctx::*;
     use crate::native_endian;
     use crate::reader::Reader;
+    #[cfg(feature = "bits")]
     use bitvec::prelude::*;
 
     #[rstest(input, expected,
@@ -101,6 +102,7 @@ mod tests {
     }
 
     // Note: Copied tests from vec.rs impl
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write,
         case::normal_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), 2.into(), vec![0xBBAA, 0xDDCC].into_boxed_slice(), bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]),
         case::normal_be([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Big, Some(16), 2.into(), vec![0xAABB, 0xCCDD].into_boxed_slice(), bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]),
diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs
index ea5cc3c..642be7d 100644
--- a/src/impls/hashmap.rs
+++ b/src/impls/hashmap.rs
@@ -231,6 +231,7 @@ mod tests {
     use crate::reader::Reader;
 
     use super::*;
+    #[cfg(feature = "bits")]
     use bitvec::prelude::*;
 
     // Macro to create a deterministic HashMap for tests
@@ -250,6 +251,7 @@ mod tests {
          };
     );
 
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes,
         case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashMap::default(), bits![u8, Msb0;], &[0xaa]),
         case::count_1([0x01, 0xAA, 0x02, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0x02, 0xbb]),
diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs
index d061aed..7f11e09 100644
--- a/src/impls/hashset.rs
+++ b/src/impls/hashset.rs
@@ -212,6 +212,7 @@ impl<T: DekuWriter<Ctx>, S, Ctx: Copy> DekuWriter<Ctx> for HashSet<T, S> {
 
 #[cfg(test)]
 mod tests {
+    #[cfg(feature = "bits")]
     use crate::bitvec::{bits, BitSlice, Msb0};
     use no_std_io::io::Cursor;
     use rstest::rstest;
@@ -221,6 +222,7 @@ mod tests {
 
     use super::*;
 
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes,
         case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashSet::default(), bits![u8, Msb0;], &[0xaa]),
         case::count_1([0xAA, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]),
@@ -285,6 +287,7 @@ mod tests {
     }
 
     // Note: These tests also exist in boxed.rs
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write,
         case::normal_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), 2.into(), vec![0xBBAA, 0xDDCC].into_iter().collect(), bits![u8, Msb0;], &[], vec![0xCC, 0xDD, 0xAA, 0xBB]),
         case::normal_be([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Big, Some(16), 2.into(), vec![0xAABB, 0xCCDD].into_iter().collect(), bits![u8, Msb0;], &[], vec![0xCC, 0xDD, 0xAA, 0xBB]),
diff --git a/src/impls/nonzero.rs b/src/impls/nonzero.rs
index a9eefc8..6000742 100644
--- a/src/impls/nonzero.rs
+++ b/src/impls/nonzero.rs
@@ -43,6 +43,7 @@ macro_rules! ImplDekuTraitsCtx {
 macro_rules! ImplDekuTraits {
     ($typ:ty, $readtype:ty) => {
         ImplDekuTraitsCtx!($typ, $readtype, (), ());
+        #[cfg(feature = "bits")]
         ImplDekuTraitsCtx!($typ, $readtype, (endian, bitsize), (Endian, BitSize));
         ImplDekuTraitsCtx!($typ, $readtype, (endian, bytesize), (Endian, ByteSize));
         ImplDekuTraitsCtx!($typ, $readtype, endian, Endian);
@@ -64,14 +65,14 @@ ImplDekuTraits!(NonZeroIsize, isize);
 
 #[cfg(test)]
 mod tests {
+    use std::io::Cursor;
+
     use hexlit::hex;
     use rstest::rstest;
-    use std::io::Cursor;
 
     use crate::reader::Reader;
 
     use super::*;
-    use bitvec::prelude::*;
 
     #[rstest(input, expected,
         case(&hex!("FF"), NonZeroU8::new(0xFF).unwrap()),
@@ -80,7 +81,7 @@ mod tests {
         case(&hex!("00"), NonZeroU8::new(0xFF).unwrap()),
     )]
     fn test_non_zero(input: &[u8], expected: NonZeroU8) {
-        let mut cursor = std::io::Cursor::new(input);
+        let mut cursor = Cursor::new(input);
         let mut reader = Reader::new(&mut cursor);
         let res_read = NonZeroU8::from_reader_with_ctx(&mut reader, ()).unwrap();
         assert_eq!(expected, res_read);
diff --git a/src/impls/primitive.rs b/src/impls/primitive.rs
index 27905c4..a3aff12 100644
--- a/src/impls/primitive.rs
+++ b/src/impls/primitive.rs
@@ -3,6 +3,7 @@ use alloc::borrow::Cow;
 use alloc::format;
 use core::convert::TryInto;
 
+#[cfg(feature = "bits")]
 use bitvec::prelude::*;
 use no_std_io::io::{Read, Seek, Write};
 
@@ -12,6 +13,7 @@ use crate::writer::Writer;
 use crate::{DekuError, DekuReader, DekuWriter};
 
 /// "Read" trait: read bits and construct type
+#[cfg(feature = "bits")]
 trait DekuRead<'a, Ctx = ()> {
     /// Read bits and construct type
     /// * **input** - Input as bits
@@ -32,8 +34,8 @@ trait DekuRead<'a, Ctx = ()> {
 }
 
 // specialize u8 for ByteSize
+#[cfg(feature = "bits")]
 impl DekuRead<'_, (Endian, ByteSize)> for u8 {
-    /// Ignore endian and byte_size, as this is a `u8`
     #[inline]
     fn read(
         input: &BitSlice<u8, Msb0>,
@@ -59,6 +61,7 @@ impl DekuReader<'_, (Endian, ByteSize)> for u8 {
         let ret = reader.read_bytes_const::<MAX_TYPE_BYTES>(&mut buf)?;
         let a = match ret {
             ReaderRet::Bytes => <u8>::from_be_bytes(buf),
+            #[cfg(feature = "bits")]
             ReaderRet::Bits(bits) => {
                 let Some(bits) = bits else {
                     return Err(DekuError::Parse(Cow::from("no bits read from reader")));
@@ -71,6 +74,7 @@ impl DekuReader<'_, (Endian, ByteSize)> for u8 {
     }
 }
 
+#[cfg(feature = "bits")]
 impl DekuWriter<(Endian, BitSize)> for u8 {
     /// Ignore endian, as this is a `u8`
     #[inline(always)]
@@ -126,6 +130,7 @@ impl DekuWriter<(Endian, ByteSize)> for u8 {
 
 macro_rules! ImplDekuReadBits {
     ($typ:ty, $inner:ty) => {
+        #[cfg(feature = "bits")]
         impl DekuRead<'_, (Endian, BitSize)> for $typ {
             #[inline(never)]
             fn read(
@@ -202,6 +207,7 @@ macro_rules! ImplDekuReadBits {
             }
         }
 
+        #[cfg(feature = "bits")]
         impl DekuReader<'_, (Endian, BitSize)> for $typ {
             #[inline(always)]
             fn from_reader_with_ctx<R: Read + Seek>(
@@ -228,6 +234,7 @@ macro_rules! ImplDekuReadBits {
 
 macro_rules! ImplDekuReadBytes {
     ($typ:ty, $inner:ty) => {
+        #[cfg(feature = "bits")]
         impl DekuRead<'_, (Endian, ByteSize)> for $typ {
             #[inline(never)]
             fn read(
@@ -268,10 +275,12 @@ macro_rules! ImplDekuReadBytes {
                             <$typ>::from_be_bytes(buf.try_into().unwrap())
                         }
                     }
+                    #[cfg(feature = "bits")]
                     ReaderRet::Bits(Some(bits)) => {
                         let a = <$typ>::read(&bits, (endian, size))?;
                         a.1
                     }
+                    #[cfg(feature = "bits")]
                     ReaderRet::Bits(None) => {
                         return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                     }
@@ -284,6 +293,7 @@ macro_rules! ImplDekuReadBytes {
 
 macro_rules! ImplDekuReadSignExtend {
     ($typ:ty, $inner:ty) => {
+        #[cfg(feature = "bits")]
         impl DekuRead<'_, (Endian, ByteSize)> for $typ {
             #[inline(never)]
             fn read(
@@ -317,6 +327,7 @@ macro_rules! ImplDekuReadSignExtend {
                             <$typ>::from_be_bytes(buf.try_into()?)
                         }
                     }
+                    #[cfg(feature = "bits")]
                     ReaderRet::Bits(bits) => {
                         let Some(bits) = bits else {
                             return Err(DekuError::Parse(Cow::from("no bits read from reader")));
@@ -334,6 +345,7 @@ macro_rules! ImplDekuReadSignExtend {
             }
         }
 
+        #[cfg(feature = "bits")]
         impl DekuRead<'_, (Endian, BitSize)> for $typ {
             #[inline(never)]
             fn read(
@@ -351,6 +363,7 @@ macro_rules! ImplDekuReadSignExtend {
             }
         }
 
+        #[cfg(feature = "bits")]
         impl DekuReader<'_, (Endian, BitSize)> for $typ {
             #[inline(always)]
             fn from_reader_with_ctx<R: Read + Seek>(
@@ -395,10 +408,12 @@ macro_rules! ForwardDekuRead {
                             <$typ>::from_be_bytes(buf)
                         }
                     }
+                    #[cfg(feature = "bits")]
                     ReaderRet::Bits(Some(bits)) => {
                         let a = <$typ>::read(&bits, (endian, ByteSize(MAX_TYPE_BYTES)))?;
                         a.1
                     }
+                    #[cfg(feature = "bits")]
                     ReaderRet::Bits(None) => {
                         return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                     }
@@ -422,6 +437,7 @@ macro_rules! ForwardDekuRead {
         }
 
         //// Only have `bit_size`, set `endian` to `Endian::default`.
+        #[cfg(feature = "bits")]
         impl DekuReader<'_, BitSize> for $typ {
             #[inline(always)]
             fn from_reader_with_ctx<R: Read + Seek>(
@@ -452,6 +468,7 @@ macro_rules! ForwardDekuRead {
 
 macro_rules! ImplDekuWrite {
     ($typ:ty) => {
+        #[cfg(feature = "bits")]
         impl DekuWriter<(Endian, BitSize)> for $typ {
             #[inline(always)]
             fn to_writer<W: Write + Seek>(
@@ -578,6 +595,7 @@ macro_rules! ImplDekuWriteOnlyEndian {
 
 macro_rules! ForwardDekuWrite {
     ($typ:ty) => {
+        #[cfg(feature = "bits")]
         impl DekuWriter<BitSize> for $typ {
             #[inline(always)]
             fn to_writer<W: Write + Seek>(
@@ -785,6 +803,7 @@ mod tests {
         native_endian!(-0.006_f64)
     );
 
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, expected, expected_rest_bits, expected_rest_bytes,
         case::normal([0xDD, 0xCC, 0xBB, 0xAA].as_ref(), Endian::Little, Some(32), 0xAABB_CCDD, bits![u8, Msb0;], &[]),
         case::normal([0xDD, 0xCC, 0xBB, 0xAA].as_ref(), Endian::Big, Some(32), 0xDDCC_BBAA, bits![u8, Msb0;], &[]),
@@ -862,6 +881,7 @@ mod tests {
         assert_eq!(expected_rest_bytes, buf);
     }
 
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, expected, expected_leftover,
         case::normal_le(0xDDCC_BBAA, Endian::Little, None, vec![0xAA, 0xBB, 0xCC, 0xDD], vec![]),
         case::normal_be(0xDDCC_BBAA, Endian::Big, None, vec![0xDD, 0xCC, 0xBB, 0xAA], vec![]),
@@ -907,6 +927,7 @@ mod tests {
         assert_hex::assert_eq_hex!(expected, writer.inner.into_inner());
     }
 
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, expected, expected_write,
         case::normal([0xDD, 0xCC, 0xBB, 0xAA].as_ref(), Endian::Little, Some(32), 0xAABB_CCDD, vec![0xDD, 0xCC, 0xBB, 0xAA]),
     )]
@@ -952,11 +973,17 @@ mod tests {
         };
     }
 
+    #[cfg(feature = "bits")]
     TestSignExtending!(test_sign_extend_i8, i8);
+    #[cfg(feature = "bits")]
     TestSignExtending!(test_sign_extend_i16, i16);
+    #[cfg(feature = "bits")]
     TestSignExtending!(test_sign_extend_i32, i32);
+    #[cfg(feature = "bits")]
     TestSignExtending!(test_sign_extend_i64, i64);
+    #[cfg(feature = "bits")]
     TestSignExtending!(test_sign_extend_i128, i128);
+    #[cfg(feature = "bits")]
     TestSignExtending!(test_sign_extend_isize, isize);
 
     macro_rules! TestSignExtendingPanic {
@@ -980,9 +1007,14 @@ mod tests {
         };
     }
 
+    #[cfg(feature = "bits")]
     TestSignExtendingPanic!(test_sign_extend_i8_panic, i8, 8);
+    #[cfg(feature = "bits")]
     TestSignExtendingPanic!(test_sign_extend_i16_panic, i16, 16);
+    #[cfg(feature = "bits")]
     TestSignExtendingPanic!(test_sign_extend_i32_panic, i32, 32);
+    #[cfg(feature = "bits")]
     TestSignExtendingPanic!(test_sign_extend_i64_panic, i64, 64);
+    #[cfg(feature = "bits")]
     TestSignExtendingPanic!(test_sign_extend_i128_panic, i128, 128);
 }
diff --git a/src/impls/slice.rs b/src/impls/slice.rs
index 4026906..15b3e18 100644
--- a/src/impls/slice.rs
+++ b/src/impls/slice.rs
@@ -96,12 +96,14 @@ where
 #[cfg(test)]
 mod tests {
     use super::*;
+    #[cfg(feature = "bits")]
     use bitvec::prelude::*;
     use rstest::rstest;
     use std::io::Cursor;
 
     use crate::{ctx::Endian, reader::Reader, writer::Writer, DekuReader};
 
+    #[cfg(feature = "bits")]
     #[rstest(input,endian,expected,
         case::normal_le([0xDD, 0xCC, 0xBB, 0xAA].as_ref(), Endian::Little, [0xCCDD, 0xAABB]),
         case::normal_be([0xDD, 0xCC, 0xBB, 0xAA].as_ref(), Endian::Big, [0xDDCC, 0xBBAA]),
diff --git a/src/impls/vec.rs b/src/impls/vec.rs
index da3bd47..33c89c9 100644
--- a/src/impls/vec.rs
+++ b/src/impls/vec.rs
@@ -176,6 +176,7 @@ impl<T: DekuWriter<Ctx>, Ctx: Copy> DekuWriter<Ctx> for Vec<T> {
 
 #[cfg(test)]
 mod tests {
+    #[cfg(feature = "bits")]
     use crate::bitvec::{bits, BitSlice, Msb0};
     use rstest::rstest;
     use std::io::Cursor;
@@ -184,6 +185,7 @@ mod tests {
 
     use super::*;
 
+    #[cfg(feature = "bits")]
     #[rstest(input, limit, expected, expected_rest_bits, expected_rest_bytes,
         case::count_0([0xAA].as_ref(), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]),
         case::count_1([0xAA, 0xBB].as_ref(), 1.into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]),
@@ -211,6 +213,7 @@ mod tests {
         assert_eq!(expected_rest_bytes, buf);
     }
 
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes,
         case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]),
         case::count_1([0xAA, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]),
@@ -271,6 +274,7 @@ mod tests {
     }
 
     // Note: These tests also exist in boxed.rs
+    #[cfg(feature = "bits")]
     #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write,
         case::normal_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), 2.into(), vec![0xBBAA, 0xDDCC], bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]),
         case::normal_be([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Big, Some(16), 2.into(), vec![0xAABB, 0xCCDD], bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]),
diff --git a/src/lib.rs b/src/lib.rs
index c20622c..845cb8e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -338,6 +338,11 @@ environment, you will see logging messages as Deku does its deserialising.
 - `DekuError` whenever possible will use a `'static str`, to make the errors compile away when following a
    guide such as [min-sized-rust](https://github.com/johnthagen/min-sized-rust).
 
+# Performance: Compile without `bitvec`
+The feature `bits` enables the `bitvec` crate to use when reading and writing, which is enabled by default.
+This however slows down the reading and writing process if your code doesn't use `bits` and the `bit_offset`
+in `from_bytes`.
+
 */
 #![warn(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
@@ -360,6 +365,7 @@ pub mod no_std_io {
 }
 
 /// re-export of bitvec
+#[cfg(feature = "bits")]
 pub mod bitvec {
     pub use bitvec::prelude::*;
     pub use bitvec::view::BitView;
@@ -568,6 +574,7 @@ pub trait DekuContainerWrite: DekuWriter<()> {
     /// assert_eq!(deku::bitvec::bitvec![1, 1, 1, 1, 0, 0, 0, 1, 1], bits);
     /// ```
     #[inline(always)]
+    #[cfg(feature = "bits")]
     fn to_bits(&self) -> Result<bitvec::BitVec<u8, bitvec::Msb0>, DekuError> {
         let mut out_buf = Vec::new();
         let mut cursor = no_std_io::Cursor::new(&mut out_buf);
diff --git a/src/reader.rs b/src/reader.rs
index d85414f..d60b5b1 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -2,6 +2,7 @@
 
 use core::cmp::Ordering;
 
+#[cfg(feature = "bits")]
 use bitvec::prelude::*;
 use no_std_io::io::{ErrorKind, Read, Seek, SeekFrom};
 
@@ -16,6 +17,7 @@ pub enum ReaderRet {
     /// Successfully read bytes
     Bytes,
     /// Successfully read bits
+    #[cfg(feature = "bits")]
     Bits(Option<BitVec<u8, Msb0>>),
 }
 
@@ -24,6 +26,7 @@ pub const MAX_BITS_AMT: usize = 128;
 
 enum Leftover {
     Byte(u8),
+    #[cfg(feature = "bits")]
     Bits(BitVec<u8, Msb0>),
 }
 
@@ -120,6 +123,7 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
     /// ```
     #[inline]
     pub fn rest(&mut self) -> Vec<bool> {
+        #[cfg(feature = "bits")]
         match &self.leftover {
             Some(Leftover::Bits(bits)) => bits.iter().by_vals().collect(),
             Some(Leftover::Byte(byte)) => {
@@ -129,6 +133,8 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
             }
             None => alloc::vec![],
         }
+        #[cfg(not(feature = "bits"))]
+        alloc::vec![]
     }
 
     /// Return true if we are at the end of a reader and there are no cached bits in the reader.
@@ -164,10 +170,20 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
     // TODO: maybe send into read_bytes() if amt >= 8
     #[inline]
     pub fn skip_bits(&mut self, amt: usize) -> Result<(), DekuError> {
-        #[cfg(feature = "logging")]
-        log::trace!("skip_bits: {amt}");
-        // Save, and keep the leftover bits since the read will most likely be less than a byte
-        self.read_bits(amt)?;
+        #[cfg(feature = "bits")]
+        {
+            #[cfg(feature = "logging")]
+            log::trace!("skip_bits: {amt}");
+            // Save, and keep the leftover bits since the read will most likely be less than a byte
+            self.read_bits(amt)?;
+        }
+
+        #[cfg(not(feature = "bits"))]
+        {
+            if amt > 0 {
+                panic!("deku features no-bits was used");
+            }
+        }
 
         Ok(())
     }
@@ -183,6 +199,7 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
     /// # Params
     /// `amt`    - Amount of bits that will be read. Must be <= [`MAX_BITS_AMT`].
     #[inline(never)]
+    #[cfg(feature = "bits")]
     pub fn read_bits(&mut self, amt: usize) -> Result<Option<BitVec<u8, Msb0>>, DekuError> {
         #[cfg(feature = "logging")]
         log::trace!("read_bits: requesting {amt} bits");
@@ -318,6 +335,7 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
     fn read_bytes_other(&mut self, amt: usize, buf: &mut [u8]) -> Result<ReaderRet, DekuError> {
         match self.leftover {
             Some(Leftover::Byte(byte)) => self.read_bytes_leftover(buf, byte, amt),
+            #[cfg(feature = "bits")]
             Some(Leftover::Bits(_)) => Ok(ReaderRet::Bits(self.read_bits(amt * 8)?)),
             _ => unreachable!(),
         }
@@ -404,6 +422,7 @@ impl<'a, R: Read + Seek> Reader<'a, R> {
     ) -> Result<ReaderRet, DekuError> {
         match self.leftover {
             Some(Leftover::Byte(byte)) => self.read_bytes_const_leftover(buf, byte),
+            #[cfg(feature = "bits")]
             Some(Leftover::Bits(_)) => Ok(ReaderRet::Bits(self.read_bits(N * 8)?)),
             _ => unreachable!(),
         }
@@ -459,6 +478,7 @@ mod tests {
     use no_std_io::io::Cursor;
 
     #[test]
+    #[cfg(feature = "bits")]
     fn test_end() {
         let input = hex!("aabb");
         let mut cursor = Cursor::new(input);
@@ -479,6 +499,7 @@ mod tests {
     }
 
     #[test]
+    #[cfg(feature = "bits")]
     fn test_bits_less() {
         let input = hex!("aa");
         let mut cursor = Cursor::new(input);
diff --git a/src/writer.rs b/src/writer.rs
index 188e0fb..bfe2576 100644
--- a/src/writer.rs
+++ b/src/writer.rs
@@ -1,6 +1,8 @@
 //! Writer for writer functions
 
+#[cfg(feature = "bits")]
 use bitvec::bitvec;
+#[cfg(feature = "bits")]
 use bitvec::{field::BitField, prelude::*};
 use no_std_io::io::{Seek, SeekFrom, Write};
 
@@ -20,6 +22,7 @@ pub const MAX_BITS_AMT: usize = 128;
 pub struct Writer<W: Write + Seek> {
     pub(crate) inner: W,
     /// Leftover bits
+    #[cfg(feature = "bits")]
     pub leftover: BitVec<u8, Msb0>,
     /// Total bits written
     pub bits_written: usize,
@@ -29,8 +32,13 @@ impl<W: Write + Seek> Seek for Writer<W> {
     fn seek(&mut self, pos: SeekFrom) -> no_std_io::io::Result<u64> {
         #[cfg(feature = "logging")]
         log::trace!("seek: {pos:?}");
+
         // clear leftover
-        self.leftover = BitVec::new();
+        #[cfg(feature = "bits")]
+        {
+            self.leftover = BitVec::new();
+        }
+
         self.inner.seek(pos)
     }
 }
@@ -41,6 +49,7 @@ impl<W: Write + Seek> Writer<W> {
     pub fn new(inner: W) -> Self {
         Self {
             inner,
+            #[cfg(feature = "bits")]
             leftover: BitVec::new(),
             bits_written: 0,
         }
@@ -49,7 +58,15 @@ impl<W: Write + Seek> Writer<W> {
     /// Return the unused bits
     #[inline]
     pub fn rest(&mut self) -> alloc::vec::Vec<bool> {
-        self.leftover.iter().by_vals().collect()
+        #[cfg(feature = "bits")]
+        {
+            self.leftover.iter().by_vals().collect()
+        }
+
+        #[cfg(not(feature = "bits"))]
+        {
+            alloc::vec![]
+        }
     }
 
     /// Write all `bits` to `Writer` buffer if bits can fit into a byte buffer.
@@ -60,6 +77,7 @@ impl<W: Write + Seek> Writer<W> {
     /// # Params
     /// `bits`    - Amount of bits that will be written. length must be <= [`MAX_BITS_AMT`].
     #[inline(never)]
+    #[cfg(feature = "bits")]
     pub fn write_bits(&mut self, bits: &BitSlice<u8, Msb0>) -> Result<(), DekuError> {
         #[cfg(feature = "logging")]
         log::trace!("attempting {} bits", bits.len());
@@ -119,6 +137,7 @@ impl<W: Write + Seek> Writer<W> {
         #[cfg(feature = "logging")]
         log::trace!("writing {} bytes: {buf:02x?}", buf.len());
 
+        #[cfg(feature = "bits")]
         if !self.leftover.is_empty() {
             #[cfg(feature = "logging")]
             log::trace!("leftover exists");
@@ -133,6 +152,14 @@ impl<W: Write + Seek> Writer<W> {
             self.bits_written += buf.len() * 8;
         }
 
+        #[cfg(not(feature = "bits"))]
+        {
+            if let Err(e) = self.inner.write_all(buf) {
+                return Err(DekuError::Io(e.kind()));
+            }
+            self.bits_written += buf.len() * 8;
+        }
+
         Ok(())
     }
 
@@ -140,6 +167,7 @@ impl<W: Write + Seek> Writer<W> {
     /// into a byte buffer
     #[inline]
     pub fn finalize(&mut self) -> Result<(), DekuError> {
+        #[cfg(feature = "bits")]
         if !self.leftover.is_empty() {
             #[cfg(feature = "logging")]
             log::trace!("finalized: {} bits leftover", self.leftover.len());
@@ -178,7 +206,8 @@ mod tests {
     use hexlit::hex;
 
     #[test]
-    fn test_writer() {
+    #[cfg(feature = "bits")]
+    fn test_writer_bits() {
         let mut out_buf = Cursor::new(vec![]);
         let mut writer = Writer::new(&mut out_buf);
 
@@ -215,4 +244,16 @@ mod tests {
             &mut vec![0xaa, 0xbb, 0xf1, 0xaa, 0x1f, 0x1a, 0xaf]
         );
     }
+
+    #[test]
+    #[cfg(feature = "bits")]
+    fn test_writer_bytes() {
+        let mut out_buf = Cursor::new(vec![]);
+        let mut writer = Writer::new(&mut out_buf);
+
+        let mut input = hex!("aa");
+        writer.write_bytes(&mut input).unwrap();
+
+        assert_eq!(&mut out_buf.into_inner(), &mut vec![0xaa]);
+    }
 }


Test patch:
diff --git a/tests/mod.rs b/tests/mod.rs
index a61389c..eece9c9 100644
--- a/tests/mod.rs
+++ b/tests/mod.rs
@@ -2,3 +2,5 @@
 
 mod test_attributes;
 mod test_compile;
+#[cfg(feature = "bits")]
+mod test_to_bits;
diff --git a/tests/test_alloc.rs b/tests/test_alloc.rs
index 5da05c8..a5348d6 100644
--- a/tests/test_alloc.rs
+++ b/tests/test_alloc.rs
@@ -7,12 +7,14 @@ use deku::prelude::*;
 #[global_allocator]
 static A: AllocCounterSystem = AllocCounterSystem;
 
+#[cfg(feature = "bits")]
 #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
 #[deku(ctx = "_endian: Endian")]
 struct NestedStruct {
     field_a: u8,
 }
 
+#[cfg(feature = "bits")]
 #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
 #[deku(id_type = "u8", ctx = "_endian: Endian")]
 enum NestedEnum {
@@ -20,6 +22,7 @@ enum NestedEnum {
     VarA(u8),
 }
 
+#[cfg(feature = "bits")]
 #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
 #[deku(id_type = "u32", bytes = 2, ctx = "_endian: Endian")]
 enum NestedEnum2 {
@@ -27,6 +30,7 @@ enum NestedEnum2 {
     VarA(u8),
 }
 
+#[cfg(feature = "bits")]
 #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
 #[deku(endian = "big")]
 struct TestDeku {
@@ -44,6 +48,7 @@ struct TestDeku {
                  //field_i: NestedEnum2,
 }
 
+#[cfg(feature = "bits")]
 mod tests {
     use alloc_counter::count_alloc;
     use hexlit::hex;
diff --git a/tests/test_attributes/test_limits/mod.rs b/tests/test_attributes/test_limits/mod.rs
index c7e74d0..b4ce847 100644
--- a/tests/test_attributes/test_limits/mod.rs
+++ b/tests/test_attributes/test_limits/mod.rs
@@ -1,3 +1,4 @@
+#[cfg(feature = "bits")]
 mod test_bits_read;
 mod test_bytes_read;
 mod test_count;
diff --git a/tests/test_attributes/test_padding/mod.rs b/tests/test_attributes/test_padding/mod.rs
index 5c1d5e7..8ac0311 100644
--- a/tests/test_attributes/test_padding/mod.rs
+++ b/tests/test_attributes/test_padding/mod.rs
@@ -2,11 +2,14 @@ use std::convert::{TryFrom, TryInto};
 
 use deku::prelude::*;
 
+#[cfg(feature = "bits")]
 mod test_pad_bits_after;
+#[cfg(feature = "bits")]
 mod test_pad_bits_before;
 mod test_pad_bytes_after;
 mod test_pad_bytes_before;
 
+#[cfg(feature = "bits")]
 #[test]
 #[allow(clippy::identity_op)]
 fn test_pad_bits_before_and_pad_bytes_before() {
@@ -34,6 +37,7 @@ fn test_pad_bits_before_and_pad_bytes_before() {
     assert_eq!(vec![0b10_000000, 0x00, 0xbb], ret_write);
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_pad_bits_after_and_pad_bytes_after() {
     #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
diff --git a/tests/test_attributes/test_padding/test_pad_bytes_after.rs b/tests/test_attributes/test_padding/test_pad_bytes_after.rs
index 846c61c..d8baa16 100644
--- a/tests/test_attributes/test_padding/test_pad_bytes_after.rs
+++ b/tests/test_attributes/test_padding/test_pad_bytes_after.rs
@@ -42,6 +42,8 @@ fn test_pad_bytes_after_not_enough() {
     let _ret_read = TestStruct::try_from(data.as_slice()).unwrap();
 }
 
+// TODO: add cfg test with updated msg for not(bits)
+#[cfg(feature = "bits")]
 #[test]
 #[should_panic(
     expected = r#"InvalidParam("Invalid padding param \"(((- 2) * 8))\": cannot convert to usize")"#
@@ -59,6 +61,8 @@ fn test_pad_bytes_after_read_err() {
     let _ret_read = TestStruct::try_from(data.as_slice()).unwrap();
 }
 
+// TODO: add cfg test with updated msg for not(bits)
+#[cfg(feature = "bits")]
 #[test]
 #[should_panic(
     expected = r#"InvalidParam("Invalid padding param \"(((- 2) * 8))\": cannot convert to usize")"#
diff --git a/tests/test_attributes/test_padding/test_pad_bytes_before.rs b/tests/test_attributes/test_padding/test_pad_bytes_before.rs
index f9a92e3..6ebbb9c 100644
--- a/tests/test_attributes/test_padding/test_pad_bytes_before.rs
+++ b/tests/test_attributes/test_padding/test_pad_bytes_before.rs
@@ -42,6 +42,8 @@ fn test_pad_bytes_before_not_enough() {
     let _ret_read = TestStruct::try_from(data.as_slice()).unwrap();
 }
 
+// TODO: add cfg test with updated msg for not(bits)
+#[cfg(feature = "bits")]
 #[test]
 #[should_panic(
     expected = r#"InvalidParam("Invalid padding param \"(((- 2) * 8))\": cannot convert to usize")"#
@@ -59,6 +61,8 @@ fn test_pad_bytes_before_read_err() {
     let _ret_read = TestStruct::try_from(data.as_slice()).unwrap();
 }
 
+// TODO: add cfg test with updated msg for not(bits)
+#[cfg(feature = "bits")]
 #[test]
 #[should_panic(
     expected = r#"InvalidParam("Invalid padding param \"(((- 2) * 8))\": cannot convert to usize")"#
diff --git a/tests/test_bit_container_size.rs b/tests/test_bit_container_size.rs
index 35c5f5e..a3ddf15 100644
--- a/tests/test_bit_container_size.rs
+++ b/tests/test_bit_container_size.rs
@@ -1,6 +1,7 @@
 use deku::prelude::*;
 use rstest::*;
 
+#[cfg(feature = "bits")]
 #[derive(Debug, Default, PartialEq, DekuWrite, DekuRead)]
 #[deku(endian = "big")]
 struct Test {
@@ -14,6 +15,7 @@ struct Test {
     field_u32_be: u32,
 }
 
+#[cfg(feature = "bits")]
 #[rstest(input,
     #[should_panic(
         expected = "InvalidParam(\"bit size 5 of input is larger than bit requested size 4\")"
diff --git a/tests/test_compile/mod.rs b/tests/test_compile/mod.rs
index 92a3768..30a680e 100644
--- a/tests/test_compile/mod.rs
+++ b/tests/test_compile/mod.rs
@@ -1,4 +1,5 @@
 #[test]
+#[cfg(feature = "bits")]
 #[cfg(not(tarpaulin))]
 #[cfg_attr(miri, ignore)]
 fn test_compile() {
diff --git a/tests/test_enum.rs b/tests/test_enum.rs
index 56e61eb..f839487 100644
--- a/tests/test_enum.rs
+++ b/tests/test_enum.rs
@@ -8,6 +8,7 @@ use rstest::*;
 /// General smoke tests for enums
 /// TODO: These should be divided into smaller tests
 
+#[cfg(feature = "bits")]
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 #[deku(id_type = "u8")]
 enum TestEnum {
@@ -32,6 +33,7 @@ enum TestEnum {
     VarDefault { id: u8, value: u8 },
 }
 
+#[cfg(feature = "bits")]
 #[rstest(input,expected,
     case(&hex!("01AB"), TestEnum::VarA(0xAB)),
     case(&hex!("0269"), TestEnum::VarB(0b0110, 0b1001)),
@@ -178,7 +180,11 @@ fn test_id_pat_with_id() {
         }
     );
     assert_eq!(input, &*v.to_bytes().unwrap());
+}
 
+#[test]
+#[cfg(feature = "bits")]
+fn id_pat_with_id_bits() {
     #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
     #[deku(id_type = "u8", bits = "2")]
     pub enum IdPatBits {
diff --git a/tests/test_from_bytes.rs b/tests/test_from_bytes.rs
index a992c5b..aac1f4d 100644
--- a/tests/test_from_bytes.rs
+++ b/tests/test_from_bytes.rs
@@ -1,5 +1,6 @@
 use deku::prelude::*;
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_from_bytes_struct() {
     #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
@@ -28,6 +29,7 @@ fn test_from_bytes_struct() {
     assert_eq!(0, i);
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_from_bytes_enum() {
     #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
diff --git a/tests/test_from_reader.rs b/tests/test_from_reader.rs
index 02c927d..1e7e87d 100644
--- a/tests/test_from_reader.rs
+++ b/tests/test_from_reader.rs
@@ -1,6 +1,7 @@
 use deku::prelude::*;
 use no_std_io::io::Seek;
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_from_reader_struct() {
     #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
@@ -21,7 +22,6 @@ fn test_from_reader_struct() {
     total_read = amt_read;
     assert_eq!(TestDeku(0b0110), ret_read);
 
-    env_logger::init();
     c.rewind().unwrap();
     let (amt_read, ret_read) = TestDeku::from_reader((&mut c, total_read)).unwrap();
     assert_eq!(amt_read, 12);
@@ -34,6 +34,7 @@ fn test_from_reader_struct() {
     assert_eq!(TestDeku(0b1010), ret_read);
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_from_reader_enum() {
     #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
diff --git a/tests/test_regression.rs b/tests/test_regression.rs
index 0bf30b8..e3a7589 100644
--- a/tests/test_regression.rs
+++ b/tests/test_regression.rs
@@ -5,6 +5,7 @@ use std::io::Cursor;
 // BitSlice to type
 //
 // https://github.com/sharksforarms/deku/issues/224
+#[cfg(feature = "bits")]
 #[test]
 fn issue_224() {
     #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
@@ -66,6 +67,7 @@ fn issue_224() {
     let _packet = Packet::from_reader((&mut c, 0)).unwrap();
 }
 
+#[cfg(feature = "bits")]
 // Extra zeroes added when reading fewer bytes than needed to fill a number
 //
 // https://github.com/sharksforarms/deku/issues/282
@@ -128,6 +130,7 @@ mod issue_282 {
 // Invalid alignment assumptions when converting doing Bits and Bytes optimizations
 //
 // https://github.com/sharksforarms/deku/issues/292
+#[cfg(feature = "bits")]
 #[test]
 fn test_regression_292() {
     let test_data = [0x0f, 0xf0];
diff --git a/tests/test_struct.rs b/tests/test_struct.rs
index c5bb117..92ed213 100644
--- a/tests/test_struct.rs
+++ b/tests/test_struct.rs
@@ -10,11 +10,13 @@ mod test_common;
 /// TODO: These should be divided into smaller tests
 
 // Common struct to test nesting
+#[cfg(feature = "bits")]
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 pub struct DoubleNestedDeku {
     pub data: u16,
 }
 
+#[cfg(feature = "bits")]
 // Common struct to test nesting
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 pub struct NestedDeku {
@@ -26,6 +28,7 @@ pub struct NestedDeku {
     pub inner: DoubleNestedDeku,
 }
 
+#[cfg(feature = "bits")]
 #[test]
 #[should_panic(expected = r#"Parse("Too much data")"#)]
 fn test_read_too_much_data() {
@@ -39,6 +42,7 @@ fn test_read_too_much_data() {
     TestStruct::try_from(test_data).unwrap();
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_unnamed_struct() {
     #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
@@ -96,6 +100,7 @@ fn test_unnamed_struct() {
     assert_eq!(test_data, ret_write);
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_named_struct() {
     #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
diff --git a/tests/test_to_bits.rs b/tests/test_to_bits.rs
index 1515a40..860aaa7 100644
--- a/tests/test_to_bits.rs
+++ b/tests/test_to_bits.rs
@@ -1,8 +1,10 @@
 use std::convert::TryFrom;
 
+#[cfg(feature = "bits")]
 use deku::bitvec::Lsb0;
 use deku::prelude::*;
 
+#[cfg(feature = "bits")]
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 pub struct Test {
     #[deku(bits = "4")]
@@ -11,6 +13,7 @@ pub struct Test {
     pub b: u8,
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_to_bits_correct() {
     let test_data: &[u8] = &[0xf1];
@@ -19,6 +22,7 @@ fn test_to_bits_correct() {
     assert_eq!(deku::bitvec::bitvec![1, 1, 1, 1, 0, 0, 0, 1], bits);
 }
 
+#[cfg(feature = "bits")]
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 pub struct TestOver {
     #[deku(bits = "4")]
@@ -29,6 +33,7 @@ pub struct TestOver {
     pub c: u8,
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_to_bits_correct_over() {
     let test_data: &[u8] = &[0xf1, 0x80];
@@ -37,6 +42,7 @@ fn test_to_bits_correct_over() {
     assert_eq!(deku::bitvec::bitvec![1, 1, 1, 1, 0, 0, 0, 1, 1], bits);
 }
 
+#[cfg(feature = "bits")]
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 #[deku(id_type = "u8", bits = "4")]
 enum TestEnum {
@@ -44,6 +50,7 @@ enum TestEnum {
     VarA,
 }
 
+#[cfg(feature = "bits")]
 #[test]
 fn test_to_bits_enum() {
     let test_data: &[u8] = &[0b1010_0000];
diff --git a/tests/test_to_slice.rs b/tests/test_to_slice.rs
index 78c6c11..e54b4c9 100644
--- a/tests/test_to_slice.rs
+++ b/tests/test_to_slice.rs
@@ -20,6 +20,7 @@ fn test_to_slice_bytes() {
     assert_eq!(amt_written, 3);
 }
 
+#[cfg(feature = "bits")]
 #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
 pub struct B {
     #[deku(bits = "3", endian = "little")]
@@ -27,6 +28,7 @@ pub struct B {
 }
 
 #[test]
+#[cfg(feature = "bits")]
 fn test_to_slice_bits() {
     let b = B { inner: 0b111 };