Repository: sharksforarms/deku
Pull request #429 (2024-04-20 22:22:12)

Add reader::read_bytes_const
* Reduce codegen for known sizes at compile time
  - DekuReader<_, Endian> for all types
  - DekuReader<_, (Endian, Endian)> for u8

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1285.6±20.73ns | **1239.8±18.39ns** | **-3.56%** |
| deku_read_byte | 5.5±0.09ns | **5.3±0.06ns** | **-3.64%** |
| deku_read_enum | 2.8±0.07ns | **2.6±0.06ns** | **-7.14%** |
| deku_read_vec | **35.2±0.57ns** | 36.3±0.48ns | **+3.12%** |
| deku_write_bits | 183.3±2.78ns | **175.0±6.16ns** | **-4.53%** |
| deku_write_byte | 22.5±0.19ns | **22.1±0.58ns** | **-1.78%** |
| deku_write_enum | 20.9±0.49ns | **20.5±0.37ns** | **-1.91%** |
| deku_write_vec | **283.2±4.84ns** | 294.6±30.34ns | **+4.03%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1283.0±17.82ns | 1298.2±26.19ns | +1.18% |
| deku_read_byte | **5.2±0.05ns** | 5.5±0.08ns | **+5.77%** |
| deku_read_enum | **2.6±0.07ns** | 2.8±0.06ns | **+7.69%** |
| deku_read_vec | **34.9±0.63ns** | 35.9±0.51ns | **+2.87%** |
| deku_write_bits | 156.2±1.57ns | 156.0±3.28ns | -0.13% |
| deku_write_byte | 22.3±1.34ns | 22.3±0.21ns | 0.00% |
| deku_write_enum | 23.5±0.42ns | **20.8±0.35ns** | **-11.49%** |
| deku_write_vec | **290.5±2.77ns** | 308.1±4.75ns | **+6.06%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | **1220.7±71.69ns** | 1262.5±41.03ns | **+3.42%** |
| deku_read_byte | 5.5±0.09ns | **5.3±0.13ns** | **-3.64%** |
| deku_read_enum | 3.0±0.18ns | **2.5±0.06ns** | **-16.67%** |
| deku_read_vec | **35.2±0.38ns** | 36.3±0.82ns | **+3.12%** |
| deku_write_bits | 159.0±4.62ns | **149.2±2.30ns** | **-6.16%** |
| deku_write_byte | 22.0±0.38ns | 22.0±0.24ns | 0.00% |
| deku_write_enum | 23.6±0.62ns | **20.5±0.64ns** | **-13.14%** |
| deku_write_vec | 306.2±13.48ns | 308.0±3.05ns | +0.59% |

  </details>
  
wcampbell0x2a: @sharksforarms should be ready now
github-actions[bot]: ## Benchmark for 8a77f7c
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | **1174.6±11.51ns** | 1232.9±17.62ns | **+4.96%** |
| deku_read_byte | **5.2±0.11ns** | 5.4±0.10ns | **+3.85%** |
| deku_read_enum | 2.6±0.10ns | 2.6±0.04ns | 0.00% |
| deku_read_vec | **35.0±0.70ns** | 36.2±0.28ns | **+3.43%** |
| deku_write_bits | 157.3±2.53ns | **152.5±3.06ns** | **-3.05%** |
| deku_write_byte | 22.0±0.19ns | 22.1±0.47ns | +0.45% |
| deku_write_enum | 23.5±0.40ns | **20.5±0.19ns** | **-12.77%** |
| deku_write_vec | **287.2±4.58ns** | 328.0±8.24ns | **+14.21%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1278.3±16.77ns | **1249.5±12.39ns** | **-2.25%** |
| deku_read_byte | 5.4±0.08ns | **3.4±0.09ns** | **-37.04%** |
| deku_read_enum | 2.6±0.05ns | 2.6±0.05ns | 0.00% |
| deku_read_vec | 36.6±0.60ns | **36.2±0.33ns** | **-1.09%** |
| deku_write_bits | 168.4±4.06ns | **155.3±1.99ns** | **-7.78%** |
| deku_write_byte | 22.1±0.70ns | 22.0±0.25ns | -0.45% |
| deku_write_enum | 20.8±0.50ns | 20.5±0.37ns | -1.44% |
| deku_write_vec | **287.8±4.65ns** | 329.5±4.85ns | **+14.49%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | **1143.7±17.76ns** | 1260.8±19.44ns | **+10.24%** |
| deku_read_byte | **5.4±0.17ns** | 5.5±0.06ns | **+1.85%** |
| deku_read_enum | **2.6±0.05ns** | 2.9±0.04ns | **+11.54%** |
| deku_read_vec | 36.5±0.64ns | **35.8±0.41ns** | **-1.92%** |
| deku_write_bits | **152.9±3.67ns** | 156.7±8.08ns | **+2.49%** |
| deku_write_byte | **22.0±0.37ns** | 24.5±6.27ns | **+11.36%** |
| deku_write_enum | 20.9±1.08ns | 20.6±0.78ns | -1.44% |
| deku_write_vec | **288.8±4.53ns** | 302.2±2.55ns | **+4.64%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | **1184.2±43.80ns** | 1267.8±14.83ns | **+7.06%** |
| deku_read_byte | 5.4±0.04ns | **3.4±0.07ns** | **-37.04%** |
| deku_read_enum | 2.6±0.06ns | 2.6±0.06ns | 0.00% |
| deku_read_vec | 36.5±0.73ns | **36.0±0.47ns** | **-1.37%** |
| deku_write_bits | **157.1±3.02ns** | 162.1±4.05ns | **+3.18%** |
| deku_write_byte | 22.0±0.38ns | 22.1±0.43ns | +0.45% |
| deku_write_enum | 20.8±0.31ns | **20.5±0.29ns** | **-1.44%** |
| deku_write_vec | **286.7±4.33ns** | 291.7±7.54ns | **+1.74%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1308.9±15.97ns | **1229.0±16.00ns** | **-6.10%** |
| deku_read_byte | 5.4±0.08ns | **3.3±0.07ns** | **-38.89%** |
| deku_read_enum | 2.6±0.07ns | 2.6±0.06ns | 0.00% |
| deku_read_vec | 33.9±0.50ns | 34.2±1.04ns | +0.88% |
| deku_write_bits | 164.1±3.98ns | **149.9±2.85ns** | **-8.65%** |
| deku_write_byte | 22.7±0.59ns | **21.9±0.44ns** | **-3.52%** |
| deku_write_enum | 21.4±0.36ns | 21.5±0.57ns | +0.47% |
| deku_write_vec | 426.0±5.52ns | **406.9±5.38ns** | **-4.48%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1277.5±20.54ns | 1262.0±36.71ns | -1.21% |
| deku_read_byte | 5.5±0.09ns | **3.3±0.10ns** | **-40.00%** |
| deku_read_enum | 3.0±0.07ns | **2.6±0.08ns** | **-13.33%** |
| deku_read_vec | 33.9±0.49ns | 33.6±0.48ns | -0.88% |
| deku_write_bits | 156.5±6.01ns | **150.0±3.65ns** | **-4.15%** |
| deku_write_byte | 22.7±0.36ns | **21.6±0.56ns** | **-4.85%** |
| deku_write_enum | 21.4±0.41ns | 21.2±0.69ns | -0.93% |
| deku_write_vec | 385.4±5.43ns | **349.6±4.72ns** | **-9.29%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1275.9±21.93ns | **1234.1±18.05ns** | **-3.28%** |
| deku_read_byte | 5.4±0.25ns | **3.3±0.09ns** | **-38.89%** |
| deku_read_enum | 2.6±0.07ns | 2.6±0.05ns | 0.00% |
| deku_read_vec | 33.9±0.42ns | 33.8±0.42ns | -0.29% |
| deku_write_bits | 159.6±4.91ns | 158.8±3.33ns | -0.50% |
| deku_write_byte | 22.9±1.77ns | **22.0±0.28ns** | **-3.93%** |
| deku_write_enum | 21.4±0.27ns | 21.4±0.23ns | 0.00% |
| deku_write_vec | 440.0±9.63ns | **347.9±3.52ns** | **-20.93%** |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1259.9±13.53ns | **1221.1±46.16ns** | **-3.08%** |
| deku_read_byte | 5.4±0.08ns | **3.3±0.03ns** | **-38.89%** |
| deku_read_enum | 2.6±0.06ns | 2.6±0.05ns | 0.00% |
| deku_read_vec | 33.9±0.21ns | 33.8±0.60ns | -0.29% |
| deku_write_bits | 165.0±16.83ns | **153.9±4.02ns** | **-6.73%** |
| deku_write_byte | 22.6±0.32ns | **21.8±0.44ns** | **-3.54%** |
| deku_write_enum | 21.4±0.32ns | 21.4±0.20ns | 0.00% |
| deku_write_vec | 429.6±6.69ns | **347.6±4.24ns** | **-19.09%** |

  </details>
  

Patch:
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index 13abbe7..3442a6f 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -1,11 +1,13 @@
 /*!
 Procedural macros that implement `DekuRead` and `DekuWrite` traits
  */
-
 #![warn(missing_docs)]
 
-use std::borrow::Cow;
+extern crate alloc;
+
+use alloc::borrow::Cow;
 use std::convert::TryFrom;
+use std::fmt::Display;
 
 use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant, ToTokens};
 use proc_macro2::TokenStream;
@@ -25,9 +27,9 @@ enum Id {
     Int(syn::LitInt),
 }
 
-impl ToString for Id {
-    fn to_string(&self) -> String {
-        self.to_token_stream().to_string()
+impl Display for Id {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.to_token_stream().to_string())
     }
 }
 
@@ -73,9 +75,9 @@ impl Num {
     }
 }
 
-impl ToString for Num {
-    fn to_string(&self) -> String {
-        self.0.to_token_stream().to_string()
+impl Display for Num {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.0.to_token_stream().to_string())
     }
 }
 
@@ -267,14 +269,12 @@ impl DekuData {
 
     /// Emit a reader. On error, a compiler error is emitted
     fn emit_reader(&self) -> TokenStream {
-        self.emit_reader_checked()
-            .map_or_else(|e| e.to_compile_error(), |tks| tks)
+        self.emit_reader_checked().unwrap_or_else(|e| e.to_compile_error())
     }
 
     /// Emit a writer. On error, a compiler error is emitted
     fn emit_writer(&self) -> TokenStream {
-        self.emit_writer_checked()
-            .map_or_else(|e| e.to_compile_error(), |tks| tks)
+        self.emit_writer_checked().unwrap_or_else(|e| e.to_compile_error())
     }
 
     /// Same as `emit_reader`, but won't auto convert error to compile error
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 37199b8..a752702 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -275,12 +275,14 @@ fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
     if !has_default_match && default_reader.is_none() {
         variant_matches.push(quote! {
             _ => {
+                extern crate alloc;
+                use alloc::borrow::Cow;
                 return Err(::#crate_::DekuError::Parse(
-                            format!(
+                            Cow::from(format!(
                                 "Could not match enum variant id = {:?} on enum `{}`",
                                 __deku_variant_id,
                                 #ident_as_string
-                            )
+                            ))
                         ));
             }
         });
@@ -440,7 +442,9 @@ fn emit_magic_read(input: &DekuData) -> TokenStream {
             for __deku_byte in __deku_magic {
                 let __deku_read_byte = u8::from_reader_with_ctx(__deku_reader, ())?;
                 if *__deku_byte != __deku_read_byte {
-                    return Err(::#crate_::DekuError::Parse(format!("Missing magic value {:?}", #magic)));
+                    extern crate alloc;
+                    use alloc::borrow::Cow;
+                    return Err(::#crate_::DekuError::Parse(Cow::from(format!("Missing magic value {:?}", #magic))));
                 }
             }
         }
@@ -514,11 +518,13 @@ fn emit_padding(bit_size: &TokenStream) -> TokenStream {
         {
             use core::convert::TryFrom;
             // TODO: I hope this consts in most cases?
+            extern crate alloc;
+            use alloc::borrow::Cow;
             let __deku_pad = usize::try_from(#bit_size).map_err(|e|
-                ::#crate_::DekuError::InvalidParam(format!(
+                ::#crate_::DekuError::InvalidParam(Cow::from(format!(
                     "Invalid padding param \"({})\": cannot convert to usize",
                     stringify!(#bit_size)
-                ))
+                )))
             )?;
 
 
@@ -820,7 +826,9 @@ pub fn emit_try_from(
                 let mut cursor = ::#crate_::no_std_io::Cursor::new(input);
                 let (amt_read, res) = <Self as ::#crate_::DekuContainerRead>::from_reader((&mut cursor, 0))?;
                 if (amt_read / 8) != total_len {
-                    return Err(::#crate_::DekuError::Parse(format!("Too much data")));
+                    extern crate alloc;
+                    use alloc::borrow::Cow;
+                    return Err(::#crate_::DekuError::Parse(Cow::from("Too much data")));
                 }
                 Ok(res)
             }
diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs
index 12ae706..3f3b90f 100644
--- a/deku-derive/src/macros/deku_write.rs
+++ b/deku-derive/src/macros/deku_write.rs
@@ -414,11 +414,13 @@ fn emit_padding(bit_size: &TokenStream) -> TokenStream {
     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(format!(
+                ::#crate_::DekuError::InvalidParam(Cow::from(format!(
                     "Invalid padding param \"({})\": cannot convert to usize",
                     stringify!(#bit_size)
-                ))
+                )))
             )?;
             __deku_writer.write_bits(::#crate_::bitvec::bitvec![u8, ::#crate_::bitvec::Msb0; 0; __deku_pad].as_bitslice())?;
         }
diff --git a/deku-derive/src/macros/mod.rs b/deku-derive/src/macros/mod.rs
index f5fbf40..8853dc4 100644
--- a/deku-derive/src/macros/mod.rs
+++ b/deku-derive/src/macros/mod.rs
@@ -372,12 +372,14 @@ fn assertion_failed(
     #[cfg(not(feature = "no-assert-string"))]
     {
         quote! {
-            return Err(::#crate_::DekuError::Assertion(format!(
+            extern crate alloc;
+            use alloc::borrow::Cow;
+            return Err(::#crate_::DekuError::Assertion(Cow::from(format!(
                 "{}.{} field failed assertion: {}",
                 #ident,
                 #field_ident_str,
                 #stringify,
-            )));
+            ))));
         }
     }
 }
diff --git a/src/error.rs b/src/error.rs
index 4ba1286..5c2151c 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,9 +1,11 @@
 //! Error module
 
 #![cfg(feature = "alloc")]
+use alloc::borrow::Cow;
+
+use no_std_io::io::ErrorKind;
 
 use alloc::format;
-use alloc::string::String;
 
 /// Number of bits needed to retry parsing
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -38,30 +40,28 @@ pub enum DekuError {
     /// Parsing error when reading
     Incomplete(NeedSize),
     /// Parsing error when reading
-    Parse(String),
+    Parse(Cow<'static, str>),
     /// Invalid parameter
-    InvalidParam(String),
-    /// Unexpected error
-    Unexpected(String),
+    InvalidParam(Cow<'static, str>),
     /// Assertion error from `assert` or `assert_eq` attributes
-    Assertion(String),
+    Assertion(Cow<'static, str>),
     /// Assertion error from `assert` or `assert_eq` attributes, without string
     AssertionNoStr,
     /// Could not resolve `id` for variant
     IdVariantNotFound,
-    /// IO error while writing
-    Write,
+    /// IO error while reading or writing
+    Io(ErrorKind),
 }
 
 impl From<core::num::TryFromIntError> for DekuError {
     fn from(e: core::num::TryFromIntError) -> DekuError {
-        DekuError::Parse(format!("error parsing int: {e}"))
+        DekuError::Parse(Cow::from(format!("error parsing int: {e}")))
     }
 }
 
 impl From<core::array::TryFromSliceError> for DekuError {
     fn from(e: core::array::TryFromSliceError) -> DekuError {
-        DekuError::Parse(format!("error parsing from slice: {e}"))
+        DekuError::Parse(Cow::from(format!("error parsing from slice: {e}")))
     }
 }
 
@@ -82,11 +82,10 @@ impl core::fmt::Display for DekuError {
             ),
             DekuError::Parse(ref err) => write!(f, "Parse error: {err}"),
             DekuError::InvalidParam(ref err) => write!(f, "Invalid param error: {err}"),
-            DekuError::Unexpected(ref err) => write!(f, "Unexpected error: {err}"),
             DekuError::Assertion(ref err) => write!(f, "Assertion error: {err}"),
             DekuError::AssertionNoStr => write!(f, "Assertion error"),
             DekuError::IdVariantNotFound => write!(f, "Could not resolve `id` for variant"),
-            DekuError::Write => write!(f, "write error"),
+            DekuError::Io(ref e) => write!(f, "io errorr: {e:?}"),
         }
     }
 }
@@ -106,11 +105,10 @@ impl From<DekuError> for std::io::Error {
             DekuError::Incomplete(_) => io::Error::new(io::ErrorKind::UnexpectedEof, error),
             DekuError::Parse(_) => io::Error::new(io::ErrorKind::InvalidData, error),
             DekuError::InvalidParam(_) => io::Error::new(io::ErrorKind::InvalidInput, error),
-            DekuError::Unexpected(_) => io::Error::new(io::ErrorKind::Other, error),
             DekuError::Assertion(_) => io::Error::new(io::ErrorKind::InvalidData, error),
             DekuError::AssertionNoStr => io::Error::from(io::ErrorKind::InvalidData),
             DekuError::IdVariantNotFound => io::Error::new(io::ErrorKind::NotFound, error),
-            DekuError::Write => io::Error::new(io::ErrorKind::BrokenPipe, error),
+            DekuError::Io(e) => io::Error::new(e, error),
         }
     }
 }
diff --git a/src/impls/bool.rs b/src/impls/bool.rs
index 34fdaba..1b1ee96 100644
--- a/src/impls/bool.rs
+++ b/src/impls/bool.rs
@@ -1,5 +1,7 @@
 use no_std_io::io::{Read, Write};
 
+#[cfg(feature = "alloc")]
+use alloc::borrow::Cow;
 #[cfg(feature = "alloc")]
 use alloc::format;
 
@@ -21,7 +23,9 @@ where
         let ret = match val {
             0x01 => Ok(true),
             0x00 => Ok(false),
-            _ => Err(DekuError::Parse(format!("cannot parse bool value: {val}",))),
+            _ => Err(DekuError::Parse(Cow::from(format!(
+                "cannot parse bool value: {val}",
+            )))),
         }?;
 
         Ok(ret)
diff --git a/src/impls/cstring.rs b/src/impls/cstring.rs
index cf636b5..02ba1c8 100644
--- a/src/impls/cstring.rs
+++ b/src/impls/cstring.rs
@@ -1,3 +1,4 @@
+use alloc::borrow::Cow;
 use no_std_io::io::{Read, Write};
 use std::ffi::CString;
 
@@ -27,8 +28,9 @@ where
         let bytes =
             Vec::from_reader_with_ctx(reader, (Limit::from(|b: &u8| *b == 0x00), inner_ctx))?;
 
-        let value = CString::from_vec_with_nul(bytes)
-            .map_err(|e| DekuError::Parse(format!("Failed to convert Vec to CString: {e}")))?;
+        let value = CString::from_vec_with_nul(bytes).map_err(|e| {
+            DekuError::Parse(Cow::from(format!("Failed to convert Vec to CString: {e}")))
+        })?;
 
         Ok(value)
     }
diff --git a/src/impls/nonzero.rs b/src/impls/nonzero.rs
index e51c1cd..62e6539 100644
--- a/src/impls/nonzero.rs
+++ b/src/impls/nonzero.rs
@@ -1,4 +1,6 @@
 #[cfg(feature = "alloc")]
+use alloc::borrow::Cow;
+#[cfg(feature = "alloc")]
 use alloc::format;
 use core::num::*;
 use no_std_io::io::{Read, Write};
@@ -19,7 +21,7 @@ macro_rules! ImplDekuTraitsCtx {
                 let value = <$typ>::new(value);
 
                 match value {
-                    None => Err(DekuError::Parse(format!("NonZero assertion"))),
+                    None => Err(DekuError::Parse(Cow::from(format!("NonZero assertion")))),
                     Some(v) => Ok(v),
                 }
             }
diff --git a/src/impls/primitive.rs b/src/impls/primitive.rs
index 4f0df03..eb7f618 100644
--- a/src/impls/primitive.rs
+++ b/src/impls/primitive.rs
@@ -1,7 +1,6 @@
+use alloc::borrow::Cow;
 #[cfg(feature = "alloc")]
 use alloc::format;
-#[cfg(feature = "alloc")]
-use alloc::string::ToString;
 use core::convert::TryInto;
 
 use bitvec::prelude::*;
@@ -79,13 +78,14 @@ impl DekuReader<'_, (Endian, ByteSize)> for u8 {
         reader: &mut Reader<R>,
         (endian, size): (Endian, ByteSize),
     ) -> Result<u8, DekuError> {
-        let mut buf = [0; core::mem::size_of::<u8>()];
-        let ret = reader.read_bytes(size.0, &mut buf)?;
+        const MAX_TYPE_BYTES: usize = core::mem::size_of::<u8>();
+        let mut buf = [0; MAX_TYPE_BYTES];
+        let ret = reader.read_bytes_const::<MAX_TYPE_BYTES>(&mut buf)?;
         let a = match ret {
             ReaderRet::Bytes => <u8>::from_be_bytes(buf),
             ReaderRet::Bits(bits) => {
                 let Some(bits) = bits else {
-                    return Err(DekuError::Parse("no bits read from reader".to_string()));
+                    return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                 };
                 let a = <u8>::read(&bits, (endian, size))?;
                 a.1
@@ -181,14 +181,14 @@ macro_rules! ImplDekuReadBits {
             ) -> Result<$typ, DekuError> {
                 const MAX_TYPE_BITS: usize = BitSize::of::<$typ>().0;
                 if size.0 > MAX_TYPE_BITS {
-                    return Err(DekuError::Parse(format!(
+                    return Err(DekuError::Parse(Cow::from(format!(
                         "too much data: container of {MAX_TYPE_BITS} bits cannot hold {} bits",
                         size.0
-                    )));
+                    ))));
                 }
                 let bits = reader.read_bits(size.0)?;
                 let Some(bits) = bits else {
-                    return Err(DekuError::Parse(format!("no bits read from reader",)));
+                    return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                 };
                 let a = <$typ>::read(&bits, (endian, size))?;
                 Ok(a.1)
@@ -230,10 +230,10 @@ macro_rules! ImplDekuReadBytes {
             ) -> Result<$typ, DekuError> {
                 const MAX_TYPE_BYTES: usize = core::mem::size_of::<$typ>();
                 if size.0 > MAX_TYPE_BYTES {
-                    return Err(DekuError::Parse(format!(
+                    return Err(DekuError::Parse(Cow::from(format!(
                         "too much data: container of {MAX_TYPE_BYTES} bytes cannot hold {} bytes",
                         size.0
-                    )));
+                    ))));
                 }
                 let mut buf = [0; MAX_TYPE_BYTES];
                 let ret = reader.read_bytes(size.0, &mut buf)?;
@@ -255,7 +255,7 @@ macro_rules! ImplDekuReadBytes {
                         a.1
                     }
                     ReaderRet::Bits(None) => {
-                        return Err(DekuError::Parse(format!("no bits read from reader")));
+                        return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                     }
                 };
                 Ok(a)
@@ -301,7 +301,7 @@ macro_rules! ImplDekuReadSignExtend {
                     }
                     ReaderRet::Bits(bits) => {
                         let Some(bits) = bits else {
-                            return Err(DekuError::Parse("no bits read from reader".to_string()));
+                            return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                         };
                         let a = <$typ>::read(&bits, (endian, size))?;
                         a.1
@@ -341,14 +341,14 @@ macro_rules! ImplDekuReadSignExtend {
             ) -> Result<$typ, DekuError> {
                 const MAX_TYPE_BITS: usize = BitSize::of::<$typ>().0;
                 if size.0 > MAX_TYPE_BITS {
-                    return Err(DekuError::Parse(format!(
+                    return Err(DekuError::Parse(Cow::from(format!(
                         "too much data: container of {MAX_TYPE_BITS} bits cannot hold {} bits",
                         size.0
-                    )));
+                    ))));
                 }
                 let bits = reader.read_bits(size.0)?;
                 let Some(bits) = bits else {
-                    return Err(DekuError::Parse(format!("no bits read from reader",)));
+                    return Err(DekuError::Parse(Cow::from("no bits read from reader")));
                 };
                 let a = <$typ>::read(&bits, (endian, size))?;
                 Ok(a.1)
@@ -357,20 +357,35 @@ macro_rules! ImplDekuReadSignExtend {
     };
 }
 
-// TODO: these forward types should forward on a ContainerCanHoldSize or something if ByteSize or
-// BitSize wasn't defined
 macro_rules! ForwardDekuRead {
     ($typ:ty) => {
-        // Only have `endian`, set `bit_size` to `Size::of::<Type>()`
+        // Only have `endian`, specialize and use read_bytes_const
         impl DekuReader<'_, Endian> for $typ {
             #[inline(always)]
             fn from_reader_with_ctx<R: Read>(
                 reader: &mut Reader<R>,
                 endian: Endian,
             ) -> Result<$typ, DekuError> {
-                const BYTE_SIZE: usize = core::mem::size_of::<$typ>();
-
-                <$typ>::from_reader_with_ctx(reader, (endian, ByteSize(BYTE_SIZE)))
+                const MAX_TYPE_BYTES: usize = core::mem::size_of::<$typ>();
+                let mut buf = [0; MAX_TYPE_BYTES];
+                let ret = reader.read_bytes_const::<MAX_TYPE_BYTES>(&mut buf)?;
+                let a = match ret {
+                    ReaderRet::Bytes => {
+                        if endian.is_le() {
+                            <$typ>::from_le_bytes(buf)
+                        } else {
+                            <$typ>::from_be_bytes(buf)
+                        }
+                    }
+                    ReaderRet::Bits(Some(bits)) => {
+                        let a = <$typ>::read(&bits, (endian, ByteSize(MAX_TYPE_BYTES)))?;
+                        a.1
+                    }
+                    ReaderRet::Bits(None) => {
+                        return Err(DekuError::Parse(Cow::from("no bits read from reader")));
+                    }
+                };
+                Ok(a)
             }
         }
 
@@ -436,11 +451,11 @@ macro_rules! ImplDekuWrite {
                 let input_bits = input.view_bits::<Msb0>();
 
                 if bit_size > input_bits.len() {
-                    return Err(DekuError::InvalidParam(format!(
+                    return Err(DekuError::InvalidParam(Cow::from(format!(
                         "bit size {} is larger than input {}",
                         bit_size,
                         input_bits.len()
-                    )));
+                    ))));
                 }
 
                 if matches!(endian, Endian::Little) {
@@ -479,10 +494,10 @@ macro_rules! ImplDekuWrite {
 
                 const TYPE_SIZE: usize = core::mem::size_of::<$typ>();
                 if size.0 > TYPE_SIZE {
-                    return Err(DekuError::InvalidParam(format!(
+                    return Err(DekuError::InvalidParam(Cow::from(format!(
                         "byte size {} is larger then input {}",
                         size.0, TYPE_SIZE
-                    )));
+                    ))));
                 }
 
                 let input = if matches!(endian, Endian::Big) {
@@ -906,11 +921,11 @@ mod tests {
                 let res_read =
                     <$typ>::from_reader_with_ctx(&mut reader, (Endian::Little, BitSize($size + 1)));
                 assert_eq!(
-                    DekuError::Parse(format!(
+                    DekuError::Parse(Cow::from(format!(
                         "too much data: container of {} bits cannot hold {} bits",
                         $size,
                         $size + 1
-                    )),
+                    ))),
                     res_read.err().unwrap()
                 );
             }
diff --git a/src/lib.rs b/src/lib.rs
index 7e14233..5c8ea8f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -227,7 +227,7 @@ assert_eq!(value.a, 0x01);
 assert_eq!(value.sub.b, 0x01 + 0x02)
 ```
 
-# `Read` enabled
+# `Read` supported
 Parsers can be created that directly read from a source implementing [Read](crate::no_std_io::Read).
 
 The crate [no_std_io] is re-exported for use in `no_std` environments.
@@ -250,7 +250,7 @@ let mut file = File::options().read(true).open("file").unwrap();
 let ec = EcHdr::from_reader((&mut file, 0)).unwrap();
 ```
 
-# `Write` enabled
+# `Write` supported
 Parsers can be created that directly write to a source implementing [Write](crate::no_std_io::Write).
 
 ```rust, no_run
@@ -332,6 +332,12 @@ Then you'd call `env_logger::init()` or `env_logger::try_init()` prior to doing
 Deku uses the `trace` logging level, so if you run your application with `RUST_LOG=trace` in your
 environment, you will see logging messages as Deku does its deserialising.
 
+# Reducing parser code size
+
+- With the use of the `no-assert-string` feature, you can remove the strings Deku adds to assertion errors.
+- `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).
+
 */
 #![warn(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
diff --git a/src/reader.rs b/src/reader.rs
index 0846802..19283e8 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -169,8 +169,7 @@ impl<'a, R: Read> Reader<'a, R> {
                     if e.kind() == ErrorKind::UnexpectedEof {
                         return Err(DekuError::Incomplete(NeedSize::new(amt)));
                     }
-
-                    // TODO: other errors?
+                    return Err(DekuError::Io(e.kind()));
                 }
                 let read_buf = &buf[..bytes_len];
 
@@ -206,6 +205,7 @@ impl<'a, R: Read> Reader<'a, R> {
     ///
     /// # Params
     /// `amt`    - Amount of bytes that will be read
+    /// `buf`    - result bytes
     #[inline(always)]
     pub fn read_bytes(&mut self, amt: usize, buf: &mut [u8]) -> Result<ReaderRet, DekuError> {
         #[cfg(feature = "logging")]
@@ -218,8 +218,7 @@ impl<'a, R: Read> Reader<'a, R> {
                 if e.kind() == ErrorKind::UnexpectedEof {
                     return Err(DekuError::Incomplete(NeedSize::new(amt * 8)));
                 }
-
-                // TODO: other errors?
+                return Err(DekuError::Io(e.kind()));
             }
 
             self.bits_read += amt * 8;
@@ -232,6 +231,38 @@ impl<'a, R: Read> Reader<'a, R> {
             Ok(ReaderRet::Bits(self.read_bits(amt * 8)?))
         }
     }
+
+    /// Attempt to read bytes from `Reader`. This will return `ReaderRet::Bytes` with a valid
+    /// `buf` of bytes if we have no "leftover" bytes and thus are byte aligned. If we are not byte
+    /// aligned, this will call `read_bits` and return `ReaderRet::Bits(_)` of size `N` * 8.
+    ///
+    /// # Params
+    /// `buf`    - result bytes
+    #[inline(always)]
+    pub fn read_bytes_const<const N: usize>(
+        &mut self,
+        buf: &mut [u8; N],
+    ) -> Result<ReaderRet, DekuError> {
+        #[cfg(feature = "logging")]
+        log::trace!("read_bytes: requesting {N} bytes");
+        if self.leftover.is_empty() {
+            if let Err(e) = self.inner.read_exact(buf) {
+                if e.kind() == ErrorKind::UnexpectedEof {
+                    return Err(DekuError::Incomplete(NeedSize::new(N * 8)));
+                }
+                return Err(DekuError::Io(e.kind()));
+            }
+
+            self.bits_read += N * 8;
+
+            #[cfg(feature = "logging")]
+            log::trace!("read_bytes: returning {:02x?}", &buf);
+
+            Ok(ReaderRet::Bytes)
+        } else {
+            Ok(ReaderRet::Bits(self.read_bits(N * 8)?))
+        }
+    }
 }
 
 #[cfg(test)]
@@ -242,12 +273,12 @@ mod tests {
 
     #[test]
     fn test_end() {
-        let input = hex!("aa");
+        let input = hex!("aabb");
         let mut cursor = Cursor::new(input);
         let mut reader = Reader::new(&mut cursor);
         assert!(!reader.end());
-        let mut buf = [0; 1];
-        let _ = reader.read_bytes(1, &mut buf);
+        let mut buf = [0; 2];
+        let _ = reader.read_bytes_const::<2>(&mut buf).unwrap();
         assert!(reader.end());
 
         let input = hex!("aa");
diff --git a/src/writer.rs b/src/writer.rs
index 7e02d69..bf4ed92 100644
--- a/src/writer.rs
+++ b/src/writer.rs
@@ -88,8 +88,8 @@ impl<W: Write> Writer<W> {
         bits = unsafe { bits.get_unchecked(count * bits_of::<u8>()..) };
 
         self.leftover = bits.to_bitvec();
-        if self.inner.write_all(buf).is_err() {
-            return Err(DekuError::Write);
+        if let Err(e) = self.inner.write_all(buf) {
+            return Err(DekuError::Io(e.kind()));
         }
 
         self.bits_written += buf.len() * 8;
@@ -117,8 +117,8 @@ impl<W: Write> Writer<W> {
             // instead of sending the entire thing. The rest would be through self.inner.write.
             self.write_bits(&BitVec::from_slice(buf))?;
         } else {
-            if self.inner.write_all(buf).is_err() {
-                return Err(DekuError::Write);
+            if let Err(e) = self.inner.write_all(buf) {
+                return Err(DekuError::Io(e.kind()));
             }
             self.bits_written += buf.len() * 8;
         }
@@ -148,8 +148,8 @@ impl<W: Write> Writer<W> {
                     *slot = byte.load_be();
                 });
 
-            if self.inner.write_all(&buf).is_err() {
-                return Err(DekuError::Write);
+            if let Err(e) = self.inner.write_all(&buf) {
+                return Err(DekuError::Io(e.kind()));
             }
             self.bits_written += buf.len() * 8;
 


Test patch:
diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs
index f945cd3..fd7fb42 100644
--- a/tests/test_attributes/test_ctx.rs
+++ b/tests/test_attributes/test_ctx.rs
@@ -1,8 +1,6 @@
 use std::convert::{TryFrom, TryInto};
 use std::io::Cursor;
 
-use bitvec::bitvec;
-use deku::bitvec::Msb0;
 use deku::prelude::*;
 use deku::reader::Reader;
 use deku::writer::Writer;
@@ -230,7 +228,6 @@ fn test_ctx_default_struct() {
     )
     .unwrap();
     assert_eq!(expected, ret_read);
-    let mut ret_write = bitvec![u8, Msb0;];
     let mut out_buf = vec![];
     let mut writer = Writer::new(&mut out_buf);
     ret_read.to_writer(&mut writer, (1, 2)).unwrap();
diff --git a/tests/test_compile/cases/internal_variables.stderr b/tests/test_compile/cases/internal_variables.stderr
index 7853121..faddbb3 100644
--- a/tests/test_compile/cases/internal_variables.stderr
+++ b/tests/test_compile/cases/internal_variables.stderr
@@ -3,3 +3,11 @@ error: Unexpected meta-item format `attribute cannot contain `__deku_` these are
    |
 88 |     #[deku(cond = "__deku_bit_offset == *field_a as usize")]
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: unused variable: `offset`
+  --> tests/test_compile/cases/internal_variables.rs:53:5
+   |
+53 |     offset: usize,
+   |     ^^^^^^ help: if this is intentional, prefix it with an underscore: `_offset`
+   |
+   = note: `#[warn(unused_variables)]` on by default
diff --git a/tests/test_compile/cases/temp_field.stderr b/tests/test_compile/cases/temp_field.stderr
index b64d3d8..f96e8bc 100644
--- a/tests/test_compile/cases/temp_field.stderr
+++ b/tests/test_compile/cases/temp_field.stderr
@@ -13,7 +13,3 @@ error: pattern requires `..` due to inaccessible fields
   |                    ^^^^^^^^^
   |
   = note: this error originates in the derive macro `DekuWrite` (in Nightly builds, run with -Z macro-backtrace for more info)
-help: ignore the inaccessible and unused fields
-  |
-4 | #[derive(DekuRead, DekuWrite { .. } {
-  |                              ~~~~~~