Repository: sharksforarms/deku
Pull request #419 (2024-02-10 16:54:23)

Remove unused DekuWrite trait
These are now unused, because internally we just now use DekuWriter

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | 1223.5±14.65ns | **1158.5±16.13ns** | **-5.31%** |
| deku_read_byte | 5.8±0.16ns | 5.8±0.05ns | 0.00% |
| deku_read_enum | 2.9±0.04ns | 2.9±0.04ns | 0.00% |
| deku_read_vec | 36.6±1.06ns | 36.4±0.38ns | -0.55% |
| deku_write_bits | 175.2±3.55ns | 174.4±2.38ns | -0.46% |
| deku_write_byte | 21.1±0.43ns | 21.1±0.47ns | 0.00% |
| deku_write_enum | 20.4±0.19ns | 20.5±0.31ns | +0.49% |
| deku_write_vec | 438.4±2.63ns | **287.9±5.82ns** | **-34.33%** |

  </details>
  
sharksforarms: @wcampbell0x2a no longer sure of the status on this one
wcampbell0x2a: > @wcampbell0x2a no longer sure of the status on this one

Now it's updated 
github-actions[bot]: ## Benchmark for 2867195
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| deku_read_bits | **1161.1±15.39ns** | 1186.5±14.93ns | **+2.19%** |
| deku_read_byte | 5.5±0.15ns | 5.4±0.13ns | -1.82% |
| deku_read_enum | 2.9±0.06ns | **2.6±0.08ns** | **-10.34%** |
| deku_read_vec | 36.1±0.58ns | 36.5±0.75ns | +1.11% |
| deku_write_bits | 163.3±3.82ns | **154.0±3.92ns** | **-5.70%** |
| deku_write_byte | 22.1±0.94ns | 22.1±0.39ns | 0.00% |
| deku_write_enum | 20.8±0.34ns | 20.8±0.45ns | 0.00% |
| deku_write_vec | 298.4±2.70ns | **286.0±4.77ns** | **-4.16%** |

  </details>
  

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index bf3139f..ae7cc56 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,6 +31,7 @@ deku_derive = { version = "^0.16.0", path = "deku-derive", default-features = fa
 bitvec = { version = "1.0.1", default-features = false }
 log = { version = "0.4.17", optional = true }
 no_std_io = { version = "0.5.0", default-features = false, features = ["alloc"] }
+rustversion = "1.0.15"
 
 [dev-dependencies]
 rstest = "0.18.0"
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..257f02e 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::*;
@@ -32,32 +31,6 @@ trait DekuRead<'a, Ctx = ()> {
         Self: Sized;
 }
 
-/// "Writer" trait: write from type to bits
-trait DekuWrite<Ctx = ()> {
-    /// Write type to bits
-    /// * **output** - Sink to store resulting bits
-    /// * **ctx** - A context required by context-sensitive reading. A unit type `()` means no context
-    /// needed.
-    fn write(
-        &self,
-        output: &mut crate::bitvec::BitVec<u8, crate::bitvec::Msb0>,
-        ctx: Ctx,
-    ) -> Result<(), DekuError>;
-}
-
-/// Implements DekuWrite for references of types that implement DekuWrite
-impl<T, Ctx> DekuWrite<Ctx> for &T
-where
-    T: DekuWrite<Ctx>,
-    Ctx: Copy,
-{
-    /// Write value of type to bits
-    fn write(&self, output: &mut BitVec<u8, Msb0>, ctx: Ctx) -> Result<(), DekuError> {
-        <T>::write(self, output, ctx)?;
-        Ok(())
-    }
-}
-
 // specialize u8 for ByteSize
 impl DekuRead<'_, (Endian, ByteSize)> for u8 {
     #[inline]
@@ -79,13 +52,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 +155,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 +204,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 +229,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 +275,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 +315,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 +331,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 +425,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 +468,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 +895,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..a5f886f 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)]
@@ -372,6 +378,13 @@ use crate::reader::Reader;
 use crate::writer::Writer;
 
 /// "Reader" trait: read bytes and bits from [`no_std_io::Read`]er
+#[rustversion::attr(
+    since(1.78),
+    diagnostic::on_unimplemented(
+        note = "implement by adding #[derive(DekuRead)] to `{Self}`",
+        note = "make sure the `ctx` sent into the function matches `{Self}`'s `ctx`",
+    )
+)]
 pub trait DekuReader<'a, Ctx = ()> {
     /// Construct type from `reader` implementing [`no_std_io::Read`], with ctx.
     ///
@@ -403,6 +416,13 @@ pub trait DekuReader<'a, Ctx = ()> {
 
 /// "Reader" trait: implemented on DekuRead struct and enum containers. A `container` is a type which
 /// doesn't need any context information.
+#[rustversion::attr(
+    since(1.78),
+    diagnostic::on_unimplemented(
+        note = "implement by adding #[derive(DekuRead)] to `{Self}`",
+        note = "make sure the `ctx` sent into the function matches `{Self}`'s `ctx`",
+    )
+)]
 pub trait DekuContainerRead<'a>: DekuReader<'a, ()> {
     /// Construct type from Reader implementing [`no_std_io::Read`].
     /// * **input** - Input given as "Reader" and bit offset
@@ -444,6 +464,13 @@ pub trait DekuContainerRead<'a>: DekuReader<'a, ()> {
 }
 
 /// "Writer" trait: write from type to bytes
+#[rustversion::attr(
+    since(1.78),
+    diagnostic::on_unimplemented(
+        note = "implement by adding #[derive(DekuRead)] to `{Self}`",
+        note = "make sure the `ctx` sent into the function matches `{Self}`'s `ctx`",
+    )
+)]
 pub trait DekuWriter<Ctx = ()> {
     /// Write type to bytes
     fn to_writer<W: no_std_io::Write>(
@@ -455,6 +482,13 @@ pub trait DekuWriter<Ctx = ()> {
 
 /// "Writer" trait: implemented on DekuWrite struct and enum containers. A `container` is a type which
 /// doesn't need any context information.
+#[rustversion::attr(
+    since(1.78),
+    diagnostic::on_unimplemented(
+        note = "implement by adding #[derive(DekuWrite)] to `{Self}`",
+        note = "make sure the `ctx` sent into the function matches `{Self}`'s `ctx`",
+    )
+)]
 pub trait DekuContainerWrite: DekuWriter<()> {
     /// Write struct/enum to Vec<u8>
     ///
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 { .. } {
-  |                              ~~~~~~