Add ostd-pod crate and #[derive(pod)], pod_union macros
This commit is contained in:
parent
d1c9d119b3
commit
c8f2cfaeae
|
|
@ -361,6 +361,7 @@ dependencies = [
|
|||
"inherit-methods-macro",
|
||||
"osdk-heap-allocator",
|
||||
"ostd",
|
||||
"ostd-pod",
|
||||
"typeflags-util",
|
||||
]
|
||||
|
||||
|
|
@ -1350,7 +1351,7 @@ dependencies = [
|
|||
"multiboot2",
|
||||
"num-traits",
|
||||
"ostd-macros",
|
||||
"ostd-pod",
|
||||
"ostd-pod 0.1.1",
|
||||
"ostd-test",
|
||||
"riscv",
|
||||
"sbi-rt",
|
||||
|
|
@ -1381,6 +1382,15 @@ dependencies = [
|
|||
"ostd-pod-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ostd-pod"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"ostd-pod-macros",
|
||||
"padding-struct",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ostd-pod-derive"
|
||||
version = "0.1.1"
|
||||
|
|
@ -1391,6 +1401,15 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ostd-pod-macros"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ostd-test"
|
||||
version = "0.17.0"
|
||||
|
|
@ -2193,18 +2212,18 @@ checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784"
|
|||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.25"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.25"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ members = [
|
|||
"ostd/libs/linux-bzimage/boot-params",
|
||||
"ostd/libs/linux-bzimage/builder",
|
||||
"ostd/libs/linux-bzimage/setup",
|
||||
"ostd/libs/ostd-pod",
|
||||
"ostd/libs/ostd-pod/macros",
|
||||
"ostd/libs/ostd-macros",
|
||||
"ostd/libs/ostd-test",
|
||||
"ostd/libs/padding-struct",
|
||||
|
|
@ -142,6 +144,7 @@ serde = { version = "1.0.192", default-features = false, features = ["alloc", "d
|
|||
smallvec = "1.13.2"
|
||||
uart_16550 = "0.3.0"
|
||||
volatile = "0.6.1"
|
||||
zerocopy = { version = "0.8.34", features = [ "derive" ] }
|
||||
|
||||
# External dependencies only for safe crates (i.e., crates under kernel or osdk/deps directories)
|
||||
#
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -212,6 +212,8 @@ NON_OSDK_CRATES := \
|
|||
ostd/libs/linux-bzimage/boot-params \
|
||||
ostd/libs/linux-bzimage/builder \
|
||||
ostd/libs/ostd-macros \
|
||||
ostd/libs/ostd-pod \
|
||||
ostd/libs/ostd-pod/macros \
|
||||
ostd/libs/ostd-test \
|
||||
ostd/libs/padding-struct \
|
||||
kernel/libs/aster-rights \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "ostd-pod"
|
||||
# REMINDER: Whenever you change this number,
|
||||
# update the external documentation links in README.md.
|
||||
version = "0.4.0"
|
||||
description = "A trait for plain old data (POD)"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ostd-pod-macros = { path = "macros", version = "0.4.0", optional = true }
|
||||
padding-struct = { path = "../padding-struct", version = "0.2.0", optional = true }
|
||||
zerocopy.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
macros = ["ostd-pod-macros", "padding-struct"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
<!--
|
||||
To promote a "single source of truth", the content of `README.md` is also included in `lib.rs`
|
||||
as the crate-level documentation. So when writing this README, bear in mind that its content
|
||||
should be recognized correctly by both a Markdown renderer and the rustdoc tool.
|
||||
-->
|
||||
|
||||
# ostd-pod
|
||||
|
||||
A trait and macros for Plain Old Data (POD) types.
|
||||
|
||||
This crate provides the [`Pod`] trait,
|
||||
which marks types that can be safely converted to and from arbitrary byte sequences.
|
||||
It's built on top of the mature [zerocopy] crate to ensure type safety.
|
||||
|
||||
## Features
|
||||
|
||||
- **Safe Byte Conversion**: POD types can be safely converted to byte sequences and created from
|
||||
byte sequences.
|
||||
- **Based on zerocopy**: Built on top of the [zerocopy] crate for type safety guarantees.
|
||||
- **Derive Macro Support**: Provides `#[derive(Pod)]` to simplify POD type definitions.
|
||||
- **Union Support**: Supports union types via the `#[pod_union]` macro.
|
||||
- **Automatic Padding Management**: Automatically handles padding bytes through the
|
||||
`#[padding_struct]` macro.
|
||||
|
||||
## What is a POD Type?
|
||||
|
||||
A POD (Plain Old Data) type is a type
|
||||
that can be safely converted to and from an arbitrary byte sequence.
|
||||
For example, primitive types like `u8` and `i16` are POD types;
|
||||
yet, `bool` is not a POD type.
|
||||
A struct whose fields are POD types is also considered a POD.
|
||||
A union whose fields are all POD types is also a POD.
|
||||
The memory layout of any POD type is `#[repr(C)]`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Edit your `Cargo.toml`
|
||||
|
||||
Add these dependencies to your `Cargo.toml`.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ostd-pod = "0.4.0"
|
||||
zerocopy = { version = "0.8.34", features = ["derive" ] }
|
||||
```
|
||||
|
||||
`zerocopy` must be explicitly specified as a dependency
|
||||
because `ostd-pod` relies on its procedural macros,
|
||||
which expand to compile-time checks that reference internal `zerocopy`
|
||||
types hardcoded to the `zerocopy` crate name.
|
||||
|
||||
### Step 2: Edit your `lib.rs` (or `main.rs`)
|
||||
|
||||
Insert the following lines to your `lib.rs` or `main.rs`:
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate ostd_pod;
|
||||
```
|
||||
|
||||
We import the `ostd_pod` crate with `extern` and `#[macro_use]`
|
||||
for the convenience of having Rust's built-in `derive` attribute macro
|
||||
globally overridden by the custom `derive` attribute macro provided by this crate.
|
||||
This custom `derive` macro is needed
|
||||
because the `Pod` trait cannot be derived in the regular way as other traits.
|
||||
|
||||
### Step 3: Define your first POD type
|
||||
|
||||
Now we can define a POD struct that
|
||||
can be converted to and from any byte sequence of the same size.
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate ostd_pod;
|
||||
use ostd_pod::{IntoBytes, FromBytes, Pod};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Pod, Clone, Copy, Debug, PartialEq)]
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let point = Point { x: 10, y: 20 };
|
||||
|
||||
// Convert to bytes
|
||||
let bytes = point.as_bytes();
|
||||
assert_eq!(bytes, &[10, 0, 0, 0, 20, 0, 0, 0]);
|
||||
|
||||
// Create from bytes
|
||||
let point2 = Point::from_bytes(bytes);
|
||||
assert_eq!(point, point2);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Use POD Unions
|
||||
|
||||
Union fields cannot be accessed safely because we cannot know which variant is currently active.
|
||||
To address this, we provide a [`pod_union`] macro
|
||||
that enables safe access to union fields.
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate ostd_pod;
|
||||
use ostd_pod::{FromZeros, IntoBytes};
|
||||
|
||||
#[pod_union]
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
union Data {
|
||||
value: u64,
|
||||
bytes: [u8; 4],
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut data = Data::new_value(0x1234567890ABCDEF);
|
||||
|
||||
// Access the same memory through different fields
|
||||
assert_eq!(*data.value(), 0x1234567890ABCDEF);
|
||||
assert_eq!(*data.bytes(), [0xEF, 0xCD, 0xAB, 0x90]);
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic Padding Handling
|
||||
|
||||
When a struct has fields with different sizes,
|
||||
there may be implicit padding bytes between fields.
|
||||
The [`padding_struct`] macro automatically inserts explicit padding fields
|
||||
so the struct can be safely used as a POD type.
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate ostd_pod;
|
||||
use ostd_pod::IntoBytes;
|
||||
|
||||
#[repr(C)]
|
||||
#[padding_struct]
|
||||
#[derive(Pod, Clone, Copy, Debug, Default)]
|
||||
struct PackedData {
|
||||
a: u8,
|
||||
// `padding_struct` automatically inserts 3 bytes of padding here
|
||||
b: u32,
|
||||
c: u16,
|
||||
// `padding_struct` automatically inserts 2 bytes of padding here
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let data = PackedData {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Can safely convert to bytes, padding bytes are explicitly handled
|
||||
let bytes = data.as_bytes();
|
||||
assert_eq!(bytes.len(), 12);
|
||||
assert_eq!(bytes, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under MPL-2.0.
|
||||
|
||||
<!--
|
||||
External links.
|
||||
-->
|
||||
[`Pod`]: https://docs.rs/ostd-pod/0.4.0/trait.Pod.html
|
||||
[`padding_struct`]: https://docs.rs/ostd-pod/0.4.0/attr.padding_struct.html
|
||||
[`pod_union`]: https://docs.rs/ostd-pod/0.4.0/attr.pod_union.html
|
||||
[zerocopy]: https://docs.rs/zerocopy/
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "ostd-pod-macros"
|
||||
version = "0.4.0"
|
||||
description = "The proc macro crate for ostd-pod"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
syn.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ostd-pod = { path = "../", default-features = false }
|
||||
zerocopy.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<!--
|
||||
To promote a "single source of truth", the content of `README.md` is also included in `lib.rs`
|
||||
as the crate-level documentation. So when writing this README, bear in mind that its content
|
||||
should be recognized correctly by both a Markdown renderer and the rustdoc tool.
|
||||
-->
|
||||
|
||||
# ostd-pod-macros
|
||||
|
||||
Procedural macros for the [ostd-pod] crate.
|
||||
|
||||
This crate provides procedural macros to simplify working with Plain Old Data (POD) types.
|
||||
It exports two main macros:
|
||||
|
||||
- `#[derive(Pod)]`: An attribute macro that expands into the underlying `zerocopy` traits
|
||||
- `#[pod_union]`: An attribute macro that makes unions safe to use as POD types
|
||||
|
||||
## The `derive` Macro
|
||||
|
||||
The `#[derive(Pod)]` macro is a convenience wrapper that automatically derives the required [zerocopy] traits for POD types.
|
||||
|
||||
Unlike typical derive procedural macros, `derive` in this crate is actually an **attribute** macro that works by shadowing [`::core::prelude::v1::derive`].
|
||||
|
||||
## The `pod_union` Macro
|
||||
|
||||
The `#[pod_union]` attribute macro enables safe usage of unions as POD types. It automatically:
|
||||
|
||||
- Derives the necessary [zerocopy] traits
|
||||
- Generates safe initializer and accessor methods for each union field
|
||||
- Enforces `#[repr(C)]` layout
|
||||
- Ensures all fields are POD types
|
||||
|
||||
### Generated Initializer and Accessor Methods
|
||||
|
||||
For each field `foo` in the union, the macro generates:
|
||||
|
||||
- `fn new_foo(value: FieldType) -> Self`: Constructs an instance from the field
|
||||
- `fn foo(&self) -> &FieldType`: Returns a reference to the field
|
||||
- `fn foo_mut(&mut self) -> &mut FieldType`: Returns a mutable reference to the field
|
||||
|
||||
These methods use `zerocopy`'s safe byte conversion methods, avoiding unsafe code.
|
||||
|
||||
For detailed usage examples, see the crate [ostd-pod] documentation.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under MPL-2.0.
|
||||
|
||||
<!--
|
||||
External links.
|
||||
-->
|
||||
[ostd-pod]: https://docs.rs/ostd-pod/
|
||||
[zerocopy]: https://docs.rs/zerocopy/
|
||||
[`::core::prelude::v1::derive`]: https://doc.rust-lang.org/core/prelude/v1/attr.derive.html
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod pod_derive;
|
||||
mod pod_union;
|
||||
|
||||
/// An attribute macro that replaces `#[derive(Pod)]` with the corresponding zerocopy traits.
|
||||
#[proc_macro_attribute]
|
||||
pub fn derive(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||
pod_derive::expand_derive(attrs, input)
|
||||
}
|
||||
|
||||
/// An attribute macro that enables safe usage of unions as POD types.
|
||||
///
|
||||
/// Rust's built-in unions cannot directly derive `zerocopy::IntoBytes` because unions require
|
||||
/// field-by-field initialization and access. The `#[pod_union]` macro solves this by
|
||||
/// transforming a union into a safe wrapper struct.
|
||||
///
|
||||
/// # Implementation details
|
||||
///
|
||||
/// When you write:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ostd_pod_macros::pod_union;
|
||||
///
|
||||
/// #[repr(C)]
|
||||
/// #[pod_union]
|
||||
/// #[derive(Clone, Copy)]
|
||||
/// pub union Data {
|
||||
/// value: u64,
|
||||
/// bytes: [u8; 4],
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `#[pod_union]` macro internally generates something equivalent to:
|
||||
///
|
||||
/// ```rust
|
||||
/// use ostd_pod::array_helper::{ArrayFactory, ArrayManufacture, U64Array};
|
||||
/// use ostd_pod::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, Pod};
|
||||
///
|
||||
/// // Internal private union
|
||||
/// #[repr(C)]
|
||||
/// #[derive(FromBytes, KnownLayout, Immutable)]
|
||||
/// union __Data__ {
|
||||
/// value: u64,
|
||||
/// bytes: [u8; 4],
|
||||
/// }
|
||||
///
|
||||
/// // Public wrapper struct that provides safe access
|
||||
/// #[repr(transparent)]
|
||||
/// #[derive(FromBytes, KnownLayout, Immutable, IntoBytes)]
|
||||
/// pub struct Data(<ArrayFactory<
|
||||
/// { align_of::<__Data__>() },
|
||||
/// { size_of::<__Data__>() / (align_of::<__Data__>()) },
|
||||
/// > as ArrayManufacture>::Array);
|
||||
///
|
||||
/// impl Data {
|
||||
/// // Field accessor methods
|
||||
/// pub fn value(&self) -> &u64 {
|
||||
/// u64::ref_from_bytes(&self.0.as_bytes()[..8]).unwrap()
|
||||
/// }
|
||||
/// pub fn value_mut(&mut self) -> &mut u64 {
|
||||
/// u64::mut_from_bytes(&mut self.0.as_mut_bytes()[..8]).unwrap()
|
||||
/// }
|
||||
/// pub fn bytes(&self) -> &[u8; 4] {
|
||||
/// <[u8; 4]>::ref_from_bytes(&self.0.as_bytes()[..4]).unwrap()
|
||||
/// }
|
||||
/// pub fn bytes_mut(&mut self) -> &mut [u8; 4] {
|
||||
/// <[u8; 4]>::mut_from_bytes(&mut self.0.as_mut_bytes()[..4]).unwrap()
|
||||
/// }
|
||||
///
|
||||
/// // Initializer methods
|
||||
/// pub fn new_value(value: u64) -> Self {
|
||||
/// let mut slf = Self::new_zeroed();
|
||||
/// *slf.value_mut() = value;
|
||||
/// slf
|
||||
/// }
|
||||
/// pub fn new_bytes(bytes: [u8; 4]) -> Self {
|
||||
/// let mut slf = Self::new_zeroed();
|
||||
/// *slf.bytes_mut() = bytes;
|
||||
/// slf
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn pod_union(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::DeriveInput);
|
||||
pod_union::expand_pod_union(input).into()
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
fn push_zerocopy_derive(
|
||||
derives: &mut Vec<proc_macro2::TokenTree>,
|
||||
ident: &str,
|
||||
trailing_comma: bool,
|
||||
) {
|
||||
use proc_macro2::{Ident, Punct, Spacing, Span, TokenTree};
|
||||
|
||||
derives.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
|
||||
derives.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
|
||||
derives.push(TokenTree::Ident(Ident::new("zerocopy", Span::call_site())));
|
||||
derives.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
|
||||
derives.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
|
||||
derives.push(TokenTree::Ident(Ident::new(ident, Span::call_site())));
|
||||
if trailing_comma {
|
||||
derives.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_derive(attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use proc_macro2::TokenTree;
|
||||
|
||||
// Process the derive attributes
|
||||
let mut new_derives = Vec::new();
|
||||
let attr_tokens = proc_macro2::TokenStream::from(attrs);
|
||||
for token in attr_tokens.into_iter() {
|
||||
match token {
|
||||
TokenTree::Ident(ident) if ident.to_string() == "Pod" => {
|
||||
// Replace Pod with zerocopy traits
|
||||
push_zerocopy_derive(&mut new_derives, "FromBytes", true);
|
||||
push_zerocopy_derive(&mut new_derives, "IntoBytes", true);
|
||||
push_zerocopy_derive(&mut new_derives, "Immutable", true);
|
||||
push_zerocopy_derive(&mut new_derives, "KnownLayout", false);
|
||||
}
|
||||
_ => {
|
||||
new_derives.push(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the output: #[::core::prelude::v1::derive(...)] + input
|
||||
let input2: proc_macro2::TokenStream = input.into();
|
||||
quote!(
|
||||
#[::core::prelude::v1::derive(#(#new_derives)*)]
|
||||
#input2
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::{
|
||||
Attribute, Data, DeriveInput, Ident, Path, Token, Visibility, parse_quote,
|
||||
punctuated::Punctuated, spanned::Spanned,
|
||||
};
|
||||
|
||||
const DERIVE_IDENT: &str = "derive";
|
||||
const REPR_IDENT: &str = "repr";
|
||||
|
||||
/// Splits attributes into non-derive attributes and derive paths
|
||||
fn split_attrs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Path>) {
|
||||
let mut other_attrs = Vec::new();
|
||||
let mut derive_paths = Vec::new();
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path().is_ident(DERIVE_IDENT) {
|
||||
let parsed: Punctuated<Path, Token![,]> = attr
|
||||
.parse_args_with(Punctuated::parse_terminated)
|
||||
.expect("failed to parse derive attribute");
|
||||
derive_paths.extend(parsed.into_iter());
|
||||
} else {
|
||||
other_attrs.push(attr);
|
||||
}
|
||||
}
|
||||
|
||||
(other_attrs, derive_paths)
|
||||
}
|
||||
|
||||
/// Checks if the attributes contain `#[repr(C)]`
|
||||
fn has_repr_c(attrs: &[Attribute]) -> bool {
|
||||
attrs.iter().any(|attr| {
|
||||
if attr.path().is_ident("repr") {
|
||||
// Parse the attribute using a custom parser
|
||||
let result = attr.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated);
|
||||
|
||||
if let Ok(list) = result {
|
||||
return list
|
||||
.iter()
|
||||
.any(|meta| matches!(meta, syn::Meta::Path(path) if path.is_ident("C")));
|
||||
}
|
||||
|
||||
false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts a path into the vector if it's not already present
|
||||
fn insert_if_absent(paths: &mut Vec<Path>, new_path: Path) {
|
||||
let new_repr = new_path.to_token_stream().to_string();
|
||||
if !paths
|
||||
.iter()
|
||||
.any(|path| path.to_token_stream().to_string() == new_repr)
|
||||
{
|
||||
paths.push(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_pod_union(input: DeriveInput) -> TokenStream2 {
|
||||
if !has_repr_c(&input.attrs) {
|
||||
panic!("`#[pod_union]` requires `#[repr(C)]` or `#[repr(C, ...)]` on unions");
|
||||
}
|
||||
|
||||
let data_union = match input.data {
|
||||
Data::Union(ref u) => u,
|
||||
_ => panic!("`#[pod_union]` can only be used on unions"),
|
||||
};
|
||||
|
||||
let vis: Visibility = input.vis.clone();
|
||||
let ident = &input.ident;
|
||||
let internal_ident = Ident::new(&format!("__{}__", ident), ident.span());
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
// Split attributes: keep non-derive attrs, collect derive paths
|
||||
let (other_attrs, derive_paths) = split_attrs(input.attrs.clone());
|
||||
|
||||
let mut union_derive_paths = derive_paths.clone();
|
||||
let mut struct_derive_paths = derive_paths;
|
||||
|
||||
// Add required zerocopy derives for internal union
|
||||
insert_if_absent(&mut union_derive_paths, parse_quote!(::zerocopy::FromBytes));
|
||||
insert_if_absent(&mut union_derive_paths, parse_quote!(::zerocopy::Immutable));
|
||||
insert_if_absent(
|
||||
&mut union_derive_paths,
|
||||
parse_quote!(::zerocopy::KnownLayout),
|
||||
);
|
||||
|
||||
// Add required zerocopy derives for public struct wrapper
|
||||
insert_if_absent(
|
||||
&mut struct_derive_paths,
|
||||
parse_quote!(::zerocopy::FromBytes),
|
||||
);
|
||||
insert_if_absent(
|
||||
&mut struct_derive_paths,
|
||||
parse_quote!(::zerocopy::Immutable),
|
||||
);
|
||||
insert_if_absent(
|
||||
&mut struct_derive_paths,
|
||||
parse_quote!(::zerocopy::IntoBytes),
|
||||
);
|
||||
insert_if_absent(
|
||||
&mut struct_derive_paths,
|
||||
parse_quote!(::zerocopy::KnownLayout),
|
||||
);
|
||||
|
||||
let union_derive_attr: Attribute = parse_quote! {
|
||||
#[derive(#(#union_derive_paths),*)]
|
||||
};
|
||||
|
||||
let struct_derive_attr: Attribute = parse_quote! {
|
||||
#[derive(#(#struct_derive_paths),*)]
|
||||
};
|
||||
|
||||
let mut union_attrs = other_attrs.clone();
|
||||
union_attrs.push(union_derive_attr);
|
||||
|
||||
let mut struct_attrs: Vec<Attribute> = other_attrs
|
||||
.into_iter()
|
||||
.filter(|attr| !attr.path().is_ident(REPR_IDENT))
|
||||
.collect();
|
||||
struct_attrs.push(parse_quote!(#[repr(transparent)]));
|
||||
struct_attrs.push(struct_derive_attr);
|
||||
|
||||
let mut internal_union = input.clone();
|
||||
internal_union.ident = internal_ident.clone();
|
||||
internal_union.vis = Visibility::Inherited;
|
||||
internal_union.attrs = union_attrs;
|
||||
|
||||
// Generate accessor methods for each field
|
||||
let accessor_methods = data_union.fields.named.iter().map(|field| {
|
||||
let field_name = &field.ident;
|
||||
let field_ty = &field.ty;
|
||||
|
||||
let ref_method_name = field_name;
|
||||
let mut_method_name = syn::Ident::new(
|
||||
&format!("{}_mut", field_name.as_ref().unwrap()),
|
||||
field_name.span(),
|
||||
);
|
||||
|
||||
quote! {
|
||||
pub fn #ref_method_name(&self) -> &#field_ty {
|
||||
use ::zerocopy::IntoBytes;
|
||||
let bytes = self.0.as_bytes();
|
||||
let slice = &bytes[..::core::mem::size_of::<#field_ty>()];
|
||||
<#field_ty as ::zerocopy::FromBytes>::ref_from_bytes(slice).unwrap()
|
||||
}
|
||||
|
||||
pub fn #mut_method_name(&mut self) -> &mut #field_ty {
|
||||
use ::zerocopy::IntoBytes;
|
||||
let bytes = self.0.as_mut_bytes();
|
||||
let slice = &mut bytes[..::core::mem::size_of::<#field_ty>()];
|
||||
<#field_ty as ::zerocopy::FromBytes>::mut_from_bytes(slice).unwrap()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Generate initializer methods for each field
|
||||
let init_methods = data_union.fields.named.iter().map(|field| {
|
||||
let field_name = field.ident.as_ref().expect("field name");
|
||||
let field_ty = &field.ty;
|
||||
let new_method_name = syn::Ident::new(&format!("new_{}", field_name), field_name.span());
|
||||
let mut_method_name = syn::Ident::new(&format!("{}_mut", field_name), field_name.span());
|
||||
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn #new_method_name(value: #field_ty) -> Self {
|
||||
use ::zerocopy::FromZeros;
|
||||
let mut slf = Self::new_zeroed();
|
||||
*slf.#mut_method_name() = value;
|
||||
slf
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Generate module name to avoid symbol conflicts
|
||||
let module_ident = syn::Ident::new(
|
||||
&format!(
|
||||
"__private_module_generated_by_ostd_pod_{}",
|
||||
ident.to_string().to_lowercase()
|
||||
),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
|
||||
// Add Copy constraint compile-time assertion
|
||||
let copy_assert = quote! {
|
||||
const _: () = {
|
||||
fn assert_copy<T: ::core::marker::Copy>() {}
|
||||
fn assert_union_copy #impl_generics() #where_clause {
|
||||
assert_copy::<#ident #ty_generics>();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Generate Pod constraint assertions for all fields
|
||||
let field_pod_asserts = data_union.fields.named.iter().map(|field| {
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
assert_pod::<#ty>();
|
||||
}
|
||||
});
|
||||
let pod_assert = quote! {
|
||||
const _: () = {
|
||||
fn assert_pod<T: ::ostd_pod::Pod>() {}
|
||||
fn assert_union_fields #impl_generics() #where_clause {
|
||||
#(#field_pod_asserts)*
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Generate the public struct
|
||||
let size_expr = quote!({ ::core::mem::size_of::<#internal_ident #ty_generics>() });
|
||||
let align_expr = quote! ({::core::mem::align_of::<#internal_ident #ty_generics>()});
|
||||
let size_align_assert = quote! {
|
||||
const _: () = {
|
||||
let size = #size_expr;
|
||||
let align = #align_expr;
|
||||
assert!(size % align == 0, "size must be a multiple of align");
|
||||
};
|
||||
};
|
||||
let internal_array = quote! {
|
||||
<::ostd_pod::array_helper::ArrayFactory<
|
||||
{ (#align_expr) },
|
||||
{ (#size_expr) / (#align_expr) }
|
||||
> as ::ostd_pod::array_helper::ArrayManufacture>::Array
|
||||
};
|
||||
let public_struct = quote! {
|
||||
#(#struct_attrs)*
|
||||
pub struct #ident #impl_generics(#internal_array) #where_clause;
|
||||
};
|
||||
|
||||
quote! {
|
||||
mod #module_ident {
|
||||
use super::*;
|
||||
|
||||
#internal_union
|
||||
|
||||
#public_struct
|
||||
|
||||
impl #impl_generics #ident #ty_generics #where_clause {
|
||||
#(#accessor_methods)*
|
||||
#(#init_methods)*
|
||||
}
|
||||
|
||||
#pod_assert
|
||||
#copy_assert
|
||||
#size_align_assert
|
||||
}
|
||||
|
||||
#vis use #module_ident::#ident;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
/// A transparent wrapper around `[u8; N]` with guaranteed 1-byte alignment.
|
||||
///
|
||||
/// This type implements the zerocopy traits (`FromBytes`, `IntoBytes`, `Immutable`, `KnownLayout`)
|
||||
/// making it safe to transmute to/from byte arrays. It is primarily used internally by the
|
||||
/// `ArrayFactory` type system to provide aligned arrays for POD unions.
|
||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct U8Array<const N: usize>([u8; N]);
|
||||
|
||||
const _: () = assert!(align_of::<U8Array<0>>() == 1);
|
||||
|
||||
/// A transparent wrapper around `[u16; N]` with guaranteed 2-byte alignment.
|
||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct U16Array<const N: usize>([u16; N]);
|
||||
|
||||
const _: () = assert!(align_of::<U16Array<0>>() == 2);
|
||||
|
||||
/// A transparent wrapper around `[u32; N]` with guaranteed 4-byte alignment.
|
||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct U32Array<const N: usize>([u32; N]);
|
||||
|
||||
const _: () = assert!(align_of::<U32Array<0>>() == 4);
|
||||
|
||||
/// A transparent wrapper around `[u64; N]` with guaranteed 8-byte alignment.
|
||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct U64Array<const N: usize>([u64; N]);
|
||||
|
||||
const _: () = assert!(align_of::<U64Array<0>>() == 8);
|
||||
|
||||
/// A type-level factory for creating aligned arrays based on alignment requirements.
|
||||
///
|
||||
/// This zero-sized type uses const generics to select the appropriate underlying array type
|
||||
/// (`U8Array`, `U16Array`, `U32Array`, or `U64Array`) based on the alignment requirement `A` and
|
||||
/// the number of elements `N`.
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `A` - The required alignment in bytes (1, 2, 4, or 8).
|
||||
/// * `N` - The number of elements in the array.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ostd_pod::array_helper::{ArrayFactory, ArrayManufacture};
|
||||
///
|
||||
/// // Creates a `U32Array<8>` (8 `u32` elements with 4-byte alignment)
|
||||
/// type MyArray = <ArrayFactory<4, 8> as ArrayManufacture>::Array;
|
||||
/// ```
|
||||
pub enum ArrayFactory<const A: usize, const N: usize> {}
|
||||
|
||||
/// Trait that associates an `ArrayFactory` with its corresponding aligned array type.
|
||||
///
|
||||
/// This trait is implemented for `ArrayFactory<A, N>` where `A` is 1, 2, 4, or 8,
|
||||
/// mapping to `U8Array`, `U16Array`, `U32Array`, and `U64Array` respectively.
|
||||
pub trait ArrayManufacture {
|
||||
/// The aligned array type produced by this factory.
|
||||
type Array: FromBytes + IntoBytes + Immutable;
|
||||
}
|
||||
|
||||
impl<const N: usize> ArrayManufacture for ArrayFactory<1, N> {
|
||||
type Array = U8Array<N>;
|
||||
}
|
||||
|
||||
impl<const N: usize> ArrayManufacture for ArrayFactory<2, N> {
|
||||
type Array = U16Array<N>;
|
||||
}
|
||||
|
||||
impl<const N: usize> ArrayManufacture for ArrayFactory<4, N> {
|
||||
type Array = U32Array<N>;
|
||||
}
|
||||
|
||||
impl<const N: usize> ArrayManufacture for ArrayFactory<8, N> {
|
||||
type Array = U64Array<N>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn u8array_alignment() {
|
||||
assert_eq!(align_of::<U8Array<0>>(), 1);
|
||||
assert_eq!(align_of::<U8Array<1>>(), 1);
|
||||
assert_eq!(align_of::<U8Array<10>>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u8array_size() {
|
||||
assert_eq!(size_of::<U8Array<0>>(), 0);
|
||||
assert_eq!(size_of::<U8Array<1>>(), 1);
|
||||
assert_eq!(size_of::<U8Array<4>>(), 4);
|
||||
assert_eq!(size_of::<U8Array<10>>(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u16array_alignment() {
|
||||
assert_eq!(align_of::<U16Array<0>>(), 2);
|
||||
assert_eq!(align_of::<U16Array<1>>(), 2);
|
||||
assert_eq!(align_of::<U16Array<10>>(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u16array_size() {
|
||||
assert_eq!(size_of::<U16Array<0>>(), 0);
|
||||
assert_eq!(size_of::<U16Array<1>>(), 2);
|
||||
assert_eq!(size_of::<U16Array<4>>(), 8);
|
||||
assert_eq!(size_of::<U16Array<10>>(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u32array_alignment() {
|
||||
assert_eq!(align_of::<U32Array<0>>(), 4);
|
||||
assert_eq!(align_of::<U32Array<1>>(), 4);
|
||||
assert_eq!(align_of::<U32Array<10>>(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u32array_size() {
|
||||
assert_eq!(size_of::<U32Array<0>>(), 0);
|
||||
assert_eq!(size_of::<U32Array<1>>(), 4);
|
||||
assert_eq!(size_of::<U32Array<4>>(), 16);
|
||||
assert_eq!(size_of::<U32Array<10>>(), 40);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u64array_alignment() {
|
||||
assert_eq!(align_of::<U64Array<0>>(), 8);
|
||||
assert_eq!(align_of::<U64Array<1>>(), 8);
|
||||
assert_eq!(align_of::<U64Array<10>>(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u64array_size() {
|
||||
assert_eq!(size_of::<U64Array<0>>(), 0);
|
||||
assert_eq!(size_of::<U64Array<1>>(), 8);
|
||||
assert_eq!(size_of::<U64Array<4>>(), 32);
|
||||
assert_eq!(size_of::<U64Array<10>>(), 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_factory_1byte_alignment() {
|
||||
type Array = <ArrayFactory<1, 5> as ArrayManufacture>::Array;
|
||||
assert_eq!(align_of::<Array>(), 1);
|
||||
assert_eq!(size_of::<Array>(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_factory_2byte_alignment() {
|
||||
type Array = <ArrayFactory<2, 5> as ArrayManufacture>::Array;
|
||||
assert_eq!(align_of::<Array>(), 2);
|
||||
assert_eq!(size_of::<Array>(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_factory_4byte_alignment() {
|
||||
type Array = <ArrayFactory<4, 5> as ArrayManufacture>::Array;
|
||||
assert_eq!(align_of::<Array>(), 4);
|
||||
assert_eq!(size_of::<Array>(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_factory_8byte_alignment() {
|
||||
type Array = <ArrayFactory<8, 5> as ArrayManufacture>::Array;
|
||||
assert_eq!(align_of::<Array>(), 8);
|
||||
assert_eq!(size_of::<Array>(), 40);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zerocopy_traits() {
|
||||
// Test that the types implement the required zerocopy traits
|
||||
fn assert_traits<T: FromBytes + IntoBytes + Immutable + KnownLayout>() {}
|
||||
|
||||
assert_traits::<U16Array<4>>();
|
||||
assert_traits::<U32Array<4>>();
|
||||
assert_traits::<U64Array<4>>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![no_std]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
pub use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
pub mod array_helper;
|
||||
|
||||
/// A trait for plain old data (POD).
|
||||
///
|
||||
/// A POD type `T: Pod` can be safely converted to and from an arbitrary byte
|
||||
/// sequence of length [`size_of::<T>()`].
|
||||
/// For example, primitive types such as `u8` and `i16` are POD types.
|
||||
///
|
||||
/// See the crate-level documentation for design notes and usage guidance.
|
||||
///
|
||||
/// [`size_of::<T>()`]: size_of
|
||||
pub trait Pod: FromBytes + IntoBytes + KnownLayout + Immutable + Copy {
|
||||
/// Creates a new instance from the given bytes.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `bytes.len() != size_of::<Self>()`.
|
||||
#[track_caller]
|
||||
fn from_bytes(bytes: &[u8]) -> Self {
|
||||
<Self as FromBytes>::read_from_bytes(bytes).unwrap()
|
||||
}
|
||||
|
||||
/// Creates a new instance by copying the first `size_of::<Self>()` bytes from `bytes`.
|
||||
///
|
||||
/// This is useful when `bytes` contains a larger buffer (e.g., a header followed by
|
||||
/// payload) and you only want to interpret the prefix as `Self`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `bytes.len() < size_of::<Self>()`.
|
||||
#[track_caller]
|
||||
fn from_first_bytes(bytes: &[u8]) -> Self {
|
||||
<Self as FromBytes>::read_from_prefix(bytes).unwrap().0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromBytes + IntoBytes + KnownLayout + Immutable + Copy> Pod for T {}
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
pub use ostd_pod_macros::{derive, pod_union};
|
||||
#[cfg(feature = "macros")]
|
||||
pub use padding_struct::padding_struct;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#[macro_use]
|
||||
extern crate ostd_pod;
|
||||
use ostd_pod::{FromZeros, IntoBytes, Pod};
|
||||
|
||||
#[test]
|
||||
fn pod_derive_simple() {
|
||||
#[repr(C)]
|
||||
#[derive(Pod, Debug, Clone, Copy, PartialEq)]
|
||||
struct S1 {
|
||||
a: u64,
|
||||
b: [u8; 8],
|
||||
}
|
||||
|
||||
let s = S1 {
|
||||
a: 42,
|
||||
b: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
};
|
||||
let bytes = s.as_bytes();
|
||||
assert_eq!(bytes.len(), size_of::<S1>());
|
||||
|
||||
let s2 = S1::from_bytes(bytes);
|
||||
assert_eq!(s, s2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pod_derive_generic() {
|
||||
#[repr(C)]
|
||||
#[derive(Pod, Clone, Copy, PartialEq, Debug)]
|
||||
struct Item<T: Pod> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
let item = Item { value: 5u64 };
|
||||
let bytes = item.as_bytes();
|
||||
assert_eq!(bytes.len(), size_of::<Item<u64>>());
|
||||
|
||||
let item2 = Item::from_bytes(bytes);
|
||||
assert_eq!(item, item2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pod_derive_zeroed() {
|
||||
#[repr(C)]
|
||||
#[derive(Pod, Copy, Clone)]
|
||||
struct Data {
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
let zeroed = Data::new_zeroed();
|
||||
assert_eq!(zeroed.x, 0);
|
||||
assert_eq!(zeroed.y, 0);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use ostd_pod::{FromZeros, IntoBytes, Pod, pod_union};
|
||||
|
||||
#[test]
|
||||
fn union_roundtrip_from_bytes() {
|
||||
#[repr(C)]
|
||||
#[pod_union]
|
||||
#[derive(Copy, Clone)]
|
||||
union U1 {
|
||||
a: u32,
|
||||
b: u64,
|
||||
}
|
||||
|
||||
let bytes: [u8; 8] = [0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11];
|
||||
let u = U1::from_bytes(&bytes);
|
||||
|
||||
assert_eq!(u.as_bytes(), &bytes);
|
||||
assert_eq!(*u.b(), 0x1122_3344_5566_7788u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_field_view_through_bytes() {
|
||||
#[repr(C)]
|
||||
#[pod_union]
|
||||
#[derive(Copy, Clone)]
|
||||
union U2 {
|
||||
a: u64,
|
||||
b: [u8; 8],
|
||||
}
|
||||
|
||||
let mut u = U2::new_zeroed();
|
||||
*u.b_mut() = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let bytes = u.as_bytes();
|
||||
assert_eq!(bytes, &[1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
assert_eq!(*u.a(), 0x0807_0605_0403_0201u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_mutable_accessor() {
|
||||
#[repr(C)]
|
||||
#[pod_union]
|
||||
#[derive(Copy, Clone)]
|
||||
union U3 {
|
||||
x: u32,
|
||||
y: [u8; 8],
|
||||
}
|
||||
|
||||
let mut u = U3::new_zeroed();
|
||||
|
||||
// Modify field through mutable accessor
|
||||
*u.x_mut() = 0xAABBCCDD;
|
||||
assert_eq!(*u.x(), 0xAABBCCDD);
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ struct MyStruct {
|
|||
|
||||
### Integration with zerocopy
|
||||
|
||||
The generated structs work seamlessly with `zerocopy` for safe transmutation:
|
||||
The generated structs work seamlessly with [`zerocopy`] for safe transmutation:
|
||||
|
||||
```rust
|
||||
use padding_struct::padding_struct;
|
||||
|
|
|
|||
Loading…
Reference in New Issue