Skip to content

Commit da948af

Browse files
committed
feat: zero-copy-derive
1 parent 5fb5198 commit da948af

20 files changed

+5253
-6
lines changed

Cargo.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ members = [
1313
"program-libs/hash-set",
1414
"program-libs/indexed-merkle-tree",
1515
"program-libs/indexed-array",
16+
"program-libs/zero-copy-derive",
1617
"programs/account-compression",
1718
"programs/system",
1819
"programs/compressed-token",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "light-zero-copy-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
description = "Proc macro for zero-copy deserialization"
7+
8+
[lib]
9+
proc-macro = true
10+
11+
[dependencies]
12+
proc-macro2 = "1.0"
13+
quote = "1.0"
14+
syn = { version = "2.0", features = ["full", "extra-traits"] }
15+
lazy_static = "1.4"
16+
17+
[dev-dependencies]
18+
trybuild = "1.0"
19+
rand = "0.8"
20+
borsh = { workspace = true }
21+
light-zero-copy = { workspace = true, features = ["std"] }
22+
zerocopy = { workspace = true, features = ["derive"] }
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Light-Zero-Copy-Derive
2+
3+
A procedural macro for deriving zero-copy deserialization for Rust structs used with Solana programs.
4+
5+
## Features
6+
7+
This crate provides two key derive macros:
8+
9+
1. `#[derive(ZeroCopy)]` - Implements zero-copy deserialization with:
10+
- The `zero_copy_at` and `zero_copy_at_mut` methods for deserialization
11+
- Full Borsh compatibility for serialization/deserialization
12+
- Efficient memory representation with no copying of data
13+
- `From<Z<StructName>>` and `From<Z<StructName>Mut>` implementations for easy conversion back to the original struct
14+
15+
2. `#[derive(ZeroCopyEq)]` - Adds equality comparison support:
16+
- Compare zero-copy instances with regular struct instances
17+
- Can be used alongside `ZeroCopy` for complete functionality
18+
- Derivation for Options<struct> is not robust and may not compile.
19+
20+
## Rules for Zero-Copy Deserialization
21+
22+
The macro follows these rules when generating code:
23+
24+
1. Creates a `ZStruct` for your struct that follows zero-copy principles
25+
1. Fields are extracted into a meta struct until reaching a `Vec`, `Option` or non-`Copy` type
26+
2. Vectors are represented as `ZeroCopySlice` and not included in the meta struct
27+
3. Integer types are replaced with their zerocopy equivalents (e.g., `u16``U16`)
28+
4. Fields after the first vector are directly included in the `ZStruct` and deserialized one by one
29+
5. If a vector contains a nested vector (non-`Copy` type), it must implement `Deserialize`
30+
6. Elements in an `Option` must implement `Deserialize`
31+
7. Types that don't implement `Copy` must implement `Deserialize` and are deserialized one by one
32+
33+
## Usage
34+
35+
### Basic Usage
36+
37+
```rust
38+
use borsh::{BorshDeserialize, BorshSerialize};
39+
use light_zero_copy_derive::ZeroCopy;
40+
use light_zero_copy::{borsh::Deserialize, borsh_mut::DeserializeMut};
41+
42+
#[repr(C)]
43+
#[derive(Debug, PartialEq, BorshSerialize, BorshDeserialize, ZeroCopy)]
44+
pub struct MyStruct {
45+
pub a: u8,
46+
pub b: u16,
47+
pub vec: Vec<u8>,
48+
pub c: u64,
49+
}
50+
let my_struct = MyStruct {
51+
a: 1,
52+
b: 2,
53+
vec: vec![1u8; 32],
54+
c: 3,
55+
};
56+
// Use the struct with zero-copy deserialization
57+
let mut bytes = my_struct.try_to_vec().unwrap();
58+
59+
// Immutable zero-copy deserialization
60+
let (zero_copy, _remaining) = MyStruct::zero_copy_at(&bytes).unwrap();
61+
62+
// Convert back to original struct using From implementation
63+
let converted: MyStruct = zero_copy.clone().into();
64+
assert_eq!(converted, my_struct);
65+
66+
// Mutable zero-copy deserialization with modification
67+
let (mut zero_copy_mut, _remaining) = MyStruct::zero_copy_at_mut(&mut bytes).unwrap();
68+
zero_copy_mut.a = 42;
69+
70+
// The change is reflected when we convert back to the original struct
71+
let modified: MyStruct = zero_copy_mut.into();
72+
assert_eq!(modified.a, 42);
73+
74+
// And also when we deserialize directly from the modified bytes
75+
let borsh = MyStruct::try_from_slice(&bytes).unwrap();
76+
assert_eq!(borsh.a, 42u8);
77+
```
78+
79+
### With Equality Comparison
80+
81+
```rust
82+
use borsh::{BorshDeserialize, BorshSerialize};
83+
use light_zero_copy_derive::ZeroCopy;
84+
85+
#[repr(C)]
86+
#[derive(Debug, PartialEq, BorshSerialize, BorshDeserialize, ZeroCopy)]
87+
pub struct MyStruct {
88+
pub a: u8,
89+
pub b: u16,
90+
pub vec: Vec<u8>,
91+
pub c: u64,
92+
}
93+
let my_struct = MyStruct {
94+
a: 1,
95+
b: 2,
96+
vec: vec![1u8; 32],
97+
c: 3,
98+
};
99+
// Use the struct with zero-copy deserialization
100+
let mut bytes = my_struct.try_to_vec().unwrap();
101+
let (zero_copy, _remaining) = MyStruct::zero_copy_at(&bytes).unwrap();
102+
assert_eq!(zero_copy, my_struct);
103+
```

0 commit comments

Comments
 (0)