Skip to content

Commit 02e4ab5

Browse files
committed
Add zerocopy support
1 parent c4a8df4 commit 02e4ab5

File tree

4 files changed

+128
-1
lines changed

4 files changed

+128
-1
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,18 @@ version = "^1.0.0"
2020
default-features = false
2121
optional = true
2222

23+
[dependencies.zerocopy]
24+
version = "0.8.9"
25+
default-features = false
26+
optional = true
27+
[dependencies.zerocopy-derive]
28+
version = "0.8.9"
29+
default-features = false
30+
optional = true
31+
2332
[features]
2433
std = []
34+
zerocopy = ["dep:zerocopy", "dep:zerocopy-derive"]
2535

2636
[workspace]
2737
members = [

src/lib.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
//!
4747
//! - [`serde`](https://serde.rs/) implements `Serialize` and `Deserialize`
4848
//! for `BitFlags<T>`.
49+
//! - [`zerocopy`](https://github.com/google/zerocopy/) implements `Immutable`, `IntoBytes`,
50+
//! `FromZeros`, `TryFromBytes`, and `KnownLayout` for all `BitFlags<T>` and `Unaligned` if the value type is unaligned.
4951
//! - `std` implements `std::error::Error` for `FromBitsError`.
5052
//!
5153
//! ## `const fn`-compatible APIs
@@ -523,6 +525,14 @@ pub use crate::const_api::ConstToken;
523525
/// `BitFlags` value where that isn't the case is only possible with
524526
/// incorrect unsafe code.
525527
#[derive(Copy, Clone)]
528+
#[cfg_attr(
529+
feature = "zerocopy",
530+
derive(
531+
zerocopy_derive::Immutable,
532+
zerocopy_derive::KnownLayout,
533+
zerocopy_derive::IntoBytes,
534+
)
535+
)]
526536
#[repr(transparent)]
527537
pub struct BitFlags<T, N = <T as _internal::RawBitFlags>::Numeric> {
528538
val: N,
@@ -1032,3 +1042,69 @@ mod impl_serde {
10321042
}
10331043
}
10341044
}
1045+
1046+
#[cfg(feature = "zerocopy")]
1047+
mod impl_zerocopy {
1048+
use super::{BitFlag, BitFlags};
1049+
use zerocopy::{FromZeros, Immutable, TryFromBytes, Unaligned};
1050+
1051+
// All zeros is always valid
1052+
unsafe impl<T> FromZeros for BitFlags<T>
1053+
where
1054+
T: BitFlag,
1055+
T::Numeric: Immutable,
1056+
T::Numeric: FromZeros,
1057+
{
1058+
fn only_derive_is_allowed_to_implement_this_trait() {}
1059+
}
1060+
1061+
// Mark all BitFlags as Unaligned if the underlying number type is unaligned
1062+
unsafe impl<T> Unaligned for BitFlags<T>
1063+
where
1064+
T: BitFlag,
1065+
T::Numeric: Unaligned,
1066+
{
1067+
fn only_derive_is_allowed_to_implement_this_trait() {}
1068+
}
1069+
1070+
// Assert that there are no invalid bytes set
1071+
unsafe impl<T> TryFromBytes for BitFlags<T>
1072+
where
1073+
T: BitFlag,
1074+
T::Numeric: Immutable,
1075+
T::Numeric: TryFromBytes,
1076+
{
1077+
fn only_derive_is_allowed_to_implement_this_trait()
1078+
where
1079+
Self: Sized,
1080+
{
1081+
}
1082+
1083+
#[inline]
1084+
fn is_bit_valid<
1085+
ZerocopyAliasing: zerocopy::pointer::invariant::Aliasing
1086+
+ zerocopy::pointer::invariant::AtLeast<zerocopy::pointer::invariant::Shared>,
1087+
>(
1088+
candidate: zerocopy::Maybe<'_, Self, ZerocopyAliasing>,
1089+
) -> bool {
1090+
// SAFETY:
1091+
// - The cast preserves address. The caller has promised that the
1092+
// cast results in an object of equal or lesser size, and so the
1093+
// cast returns a pointer which references a subset of the bytes
1094+
// of `p`.
1095+
// - The cast preserves provenance.
1096+
// - The caller has promised that the destination type has
1097+
// `UnsafeCell`s at the same byte ranges as the source type.
1098+
let candidate = unsafe { candidate.cast_unsized::<T::Numeric, _>(|p| p as *mut _) };
1099+
1100+
// SAFETY: The caller has promised that the referenced memory region
1101+
// will contain a valid `$repr`.
1102+
let my_candidate =
1103+
unsafe { candidate.assume_validity::<zerocopy::pointer::invariant::Valid>() };
1104+
{
1105+
(my_candidate.read_unaligned::<zerocopy::pointer::BecauseImmutable>() ^ T::ALL_BITS)
1106+
== T::EMPTY
1107+
}
1108+
}
1109+
}
1110+
}

test_suite/Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ edition = "2018"
66

77
[dependencies.enumflags2]
88
path = "../"
9-
features = ["serde"]
9+
features = ["serde", "zerocopy"]
1010

1111
[dependencies.serde]
1212
version = "1"
1313
features = ["derive"]
1414

15+
[dependencies.zerocopy]
16+
version = "0.8.9"
17+
features = ["derive"]
18+
1519
[dev-dependencies]
1620
trybuild = "1.0"
1721
glob = "0.3"
@@ -65,3 +69,8 @@ edition = "2018"
6569
name = "not_literal"
6670
path = "tests/not_literal.rs"
6771
edition = "2018"
72+
73+
[[test]]
74+
name = "zerocopy"
75+
path = "tests/zerocopy.rs"
76+
edition = "2018"

test_suite/tests/zerocopy.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use enumflags2::{bitflags, BitFlags};
2+
use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes};
3+
4+
#[test]
5+
fn zerocopy_compile() {
6+
#[bitflags]
7+
#[derive(Copy, Clone, Debug, KnownLayout)]
8+
#[repr(u8)]
9+
enum TestU8 {
10+
A,
11+
B,
12+
C,
13+
D,
14+
}
15+
16+
#[bitflags]
17+
#[derive(Copy, Clone, Debug, KnownLayout)]
18+
#[repr(u16)]
19+
enum TestU16 {
20+
A,
21+
B,
22+
C,
23+
D,
24+
}
25+
26+
#[derive(Clone, Debug, Immutable, TryFromBytes, IntoBytes, KnownLayout)]
27+
#[repr(packed)]
28+
struct Other {
29+
flags2: BitFlags<TestU8>,
30+
flags: BitFlags<TestU16>,
31+
}
32+
}

0 commit comments

Comments
 (0)