Skip to content

Commit 4d51c8a

Browse files
committed
Add ZeroCopy marker and SchemaRead support
1 parent 4090ff9 commit 4d51c8a

File tree

6 files changed

+445
-21
lines changed

6 files changed

+445
-21
lines changed

wincode-derive/src/schema_read.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use {
1010
},
1111
proc_macro2::TokenStream,
1212
quote::{format_ident, quote},
13-
syn::{parse_quote, DeriveInput, GenericParam, Generics, Type},
13+
syn::{
14+
parse_quote, punctuated::Punctuated, DeriveInput, GenericParam, Generics, PredicateType,
15+
Type, WhereClause, WherePredicate,
16+
},
1417
};
1518

1619
fn impl_struct(
@@ -386,10 +389,48 @@ pub(crate) fn generate(input: DeriveInput) -> Result<TokenStream> {
386389
}
387390
};
388391

392+
// Provide a `ZeroCopy` impl for the type if its `repr` is eligible and all its fields are zero-copy.
393+
let zero_copy_impl = match &args.data {
394+
Data::Struct(fields) if repr.is_zero_copy_eligible() => {
395+
// Ensure all fields are zero-copy.
396+
let inner_constraint_predicates = fields.iter().map(|field| {
397+
let mut bounds = Punctuated::new();
398+
bounds.push(parse_quote!(ZeroCopy));
399+
WherePredicate::Type(PredicateType {
400+
// Workaround for https://github.com/rust-lang/rust/issues/48214.
401+
lifetimes: Some(parse_quote!(for<'_wincode_internal>)),
402+
bounded_ty: field.target_resolved(),
403+
colon_token: parse_quote![:],
404+
bounds,
405+
})
406+
});
407+
let (impl_generics, ty_generics, where_clause) = args.generics.split_for_impl();
408+
let mut where_clause = where_clause.cloned();
409+
410+
match &mut where_clause {
411+
Some(where_clause) => {
412+
where_clause.predicates.extend(inner_constraint_predicates);
413+
}
414+
None => {
415+
where_clause = Some(WhereClause {
416+
where_token: parse_quote!(where),
417+
predicates: inner_constraint_predicates.collect(),
418+
});
419+
}
420+
}
421+
quote! {
422+
unsafe impl #impl_generics ZeroCopy for #ident #ty_generics #where_clause {}
423+
}
424+
}
425+
_ => quote!(),
426+
};
427+
389428
Ok(quote! {
390429
const _: () = {
391430
use core::{ptr, mem::{self, MaybeUninit}};
392-
use #crate_name::{SchemaRead, ReadResult, TypeMeta, io::Reader, error};
431+
use #crate_name::{SchemaRead, ReadResult, TypeMeta, io::Reader, error, ZeroCopy};
432+
433+
#zero_copy_impl
393434

394435
impl #impl_generics SchemaRead<'de> for #ident #ty_generics #where_clause {
395436
type Dst = #src_dst;

wincode/src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,19 @@ pub enum ReadError {
4444
InvalidCharLead(u8),
4545
#[error("Custom error: {0}")]
4646
Custom(&'static str),
47+
#[error("Zero-copy read would be unaligned")]
48+
UnalignedPointerRead,
4749
}
4850

4951
pub type Result<T> = core::result::Result<T, Error>;
5052
pub type WriteResult<T> = core::result::Result<T, WriteError>;
5153
pub type ReadResult<T> = core::result::Result<T, ReadError>;
5254

55+
#[cold]
56+
pub const fn unaligned_pointer_read() -> ReadError {
57+
ReadError::UnalignedPointerRead
58+
}
59+
5360
#[cold]
5461
pub const fn preallocation_size_limit(needed: usize, limit: usize) -> ReadError {
5562
ReadError::PreallocationSizeLimit { needed, limit }

wincode/src/lib.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,32 @@
198198
//! [`containers`] match the layout implied by your `serde` types.
199199
//! - Length encodings are pluggable via [`SeqLen`](len::SeqLen).
200200
//!
201-
//! # Zero copy deserialization
201+
//! # Zero-copy deserialization
202202
//!
203-
//! `wincode` supports zero copy deserialization of contiguous byte slices
204-
//! (serialized with `Vec<u8>`, `Box<[u8]>`, `[u8; N]`, etc.).
203+
//! `wincode`'s zero-copy deserialization is built on the following primitives:
204+
//! - [`u8`]
205+
//! - [`i8`]
205206
//!
207+
//! Within `wincode`, any type that is composed entirely of these primitives is
208+
//! eligible for zero-copy deserialization.
209+
//!
210+
//! Let `Z` be the set of zero-copy types.
211+
//!
212+
//! The following higher order types are eligible for zero-copy deserialization:
213+
//! - `&[Z]`
214+
//! - `&Z`
215+
//! - `&[Z; N]`
216+
//!
217+
//! Structs deriving [`SchemaWrite`] and [`SchemaRead`] are eligible for zero-copy deserialization
218+
//! as long as they are composed entirely of the above zero-copy types and are annotated with
219+
//! `#[repr(transparent)]` or `#[repr(C)]`.
220+
//! Note that this is **not** true for tuples, as Rust does not currently guarantee tuple layout.
221+
//!
222+
//! ## Examples
223+
//!
224+
//! ### `&[u8]`
206225
//! ```
207-
//! # #[cfg(feature = "derive")] {
226+
//! # #[cfg(all(feature = "alloc", feature = "derive"))] {
208227
//! use wincode::{SchemaWrite, SchemaRead};
209228
//!
210229
//! # #[derive(Debug, PartialEq, Eq)]
@@ -216,10 +235,59 @@
216235
//! let bytes: Vec<u8> = vec![1, 2, 3, 4, 5];
217236
//! let byte_ref = ByteRef { bytes: &bytes };
218237
//! let serialized = wincode::serialize(&byte_ref).unwrap();
219-
//! let deserialized = wincode::deserialize(&serialized).unwrap();
238+
//! let deserialized: ByteRef<'_> = wincode::deserialize(&serialized).unwrap();
220239
//! assert_eq!(byte_ref, deserialized);
221240
//! # }
222241
//! ```
242+
//!
243+
//! ### struct newtype
244+
//! ```
245+
//! # #[cfg(all(feature = "alloc", feature = "derive"))] {
246+
//! # use rand::random;
247+
//! # use std::array;
248+
//! use wincode::{SchemaWrite, SchemaRead};
249+
//!
250+
//! # #[derive(Debug, PartialEq, Eq)]
251+
//! #[derive(SchemaWrite, SchemaRead)]
252+
//! #[repr(transparent)]
253+
//! struct Signature([u8; 64]);
254+
//!
255+
//! # #[derive(Debug, PartialEq, Eq)]
256+
//! #[derive(SchemaWrite, SchemaRead)]
257+
//! struct Data<'a> {
258+
//! signature: &'a Signature,
259+
//! data: &'a [u8],
260+
//! }
261+
//!
262+
//! let signature = Signature(array::from_fn(|_| random()));
263+
//! let data = Data {
264+
//! signature: &signature,
265+
//! data: &[1, 2, 3, 4, 5],
266+
//! };
267+
//! let serialized = wincode::serialize(&data).unwrap();
268+
//! let deserialized: Data<'_> = wincode::deserialize(&serialized).unwrap();
269+
//! assert_eq!(data, deserialized);
270+
//! # }
271+
//! ```
272+
//!
273+
//! ### `&[u8; N]`
274+
//! ```
275+
//! # #[cfg(all(feature = "alloc", feature = "derive"))] {
276+
//! use wincode::{SchemaWrite, SchemaRead};
277+
//!
278+
//! # #[derive(Debug, PartialEq, Eq)]
279+
//! #[derive(SchemaWrite, SchemaRead)]
280+
//! struct HeaderRef<'a> {
281+
//! magic: &'a [u8; 7],
282+
//! }
283+
//!
284+
//! let header = HeaderRef { magic: b"W1NC0D3" };
285+
//! let serialized = wincode::serialize(&header).unwrap();
286+
//! let deserialized: HeaderRef<'_> = wincode::deserialize(&serialized).unwrap();
287+
//! assert_eq!(header, deserialized);
288+
//! # }
289+
//! ```
290+
//!
223291
//! # Derive attributes
224292
//!
225293
//! ## Top level

wincode/src/schema/containers.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ use {
8787
error::{ReadResult, WriteResult},
8888
io::{Reader, Writer},
8989
schema::{SchemaRead, SchemaWrite},
90-
TypeMeta,
90+
TypeMeta, ZeroCopy,
9191
},
9292
core::{marker::PhantomData, mem::MaybeUninit, ptr},
9393
};
@@ -262,6 +262,13 @@ pub struct Elem<T>(PhantomData<T>);
262262
/// ```
263263
pub struct Pod<T: Copy + 'static>(PhantomData<T>);
264264

265+
// SAFETY:
266+
// - By using `Pod`, user asserts that the type is zero-copy, given the contract of Pod:
267+
// - The type's in‑memory representation is exactly its serialized bytes.
268+
// - It can be safely initialized by memcpy (no validation, no endianness/layout work).
269+
// - Does not contain references or pointers.
270+
unsafe impl<T> ZeroCopy for Pod<T> where T: Copy + 'static {}
271+
265272
impl<T> SchemaWrite for Pod<T>
266273
where
267274
T: Copy + 'static,

0 commit comments

Comments
 (0)