const_field_offset/lib.rs
1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/*!
5This crate provides the [`FieldOffsets`] derive macro to obtain constant
6[`FieldOffset`]s for fields of a `#[repr(C)]` struct.
7
8The [`FieldOffset`] type is re-exported from the `field-offset` crate.
9
10## Usage
11
12```rust
13use const_field_offset::{FieldOffsets, FieldOffset};
14
15#[derive(FieldOffsets)]
16#[repr(C)]
17struct Foo { a: u32, b: f64 }
18
19let foo_b: FieldOffset<Foo, f64> = Foo::FIELD_OFFSETS.b();
20assert_eq!(*foo_b.apply(&Foo { a: 1, b: 42.0 }), 42.0);
21```
22
23The `FIELD_OFFSETS` constant is a zero-sized type with a const fn method
24per field. Each method returns the corresponding [`FieldOffset`].
25
26For pin-projecting offsets, use `#[pin]`:
27
28```rust
29use const_field_offset::{FieldOffsets, FieldOffset, AllowPin};
30
31#[derive(FieldOffsets)]
32#[repr(C)]
33#[pin]
34struct Foo { a: u32, b: f64 }
35
36let foo_b: FieldOffset<Foo, f64, AllowPin> = Foo::FIELD_OFFSETS.b();
37let pinned = Box::pin(Foo { a: 1, b: 42.0 });
38assert_eq!(*foo_b.apply_pin(pinned.as_ref()), 42.0);
39```
40
41The `#[pin]` attribute enforces that the struct is `!Unpin` and does not
42implement `Drop` (use `#[pin_drop]` with [`PinnedDrop`] instead).
43*/
44#![no_std]
45
46#[cfg(test)]
47extern crate alloc;
48
49use core::pin::Pin;
50
51#[doc(inline)]
52pub use const_field_offset_macro::FieldOffsets;
53
54pub use field_offset::{AllowPin, FieldOffset, NotPinned};
55
56/// Compose two pinned field offsets into the offset of the nested field.
57///
58/// Same as the `Add` implementation on [`FieldOffset`], but usable in const
59/// context.
60pub const fn compose_field_offsets<T, U, V>(
61 a: FieldOffset<T, U, AllowPin>,
62 b: FieldOffset<U, V, AllowPin>,
63) -> FieldOffset<T, V, AllowPin> {
64 // Safety: a field of a field of T is at the sum of both offsets within T,
65 // and both projections preserve pinning.
66 unsafe { FieldOffset::new_from_offset_pinned(a.get_byte_offset() + b.get_byte_offset()) }
67}
68
69/// This trait needs to be implemented if you use the `#[pin_drop]` attribute. It enables
70/// you to implement Drop for your type safely.
71pub trait PinnedDrop {
72 /// This is the equivalent to the regular Drop trait with the difference that self
73 /// is pinned.
74 fn drop(self: Pin<&mut Self>);
75
76 #[doc(hidden)]
77 fn do_safe_pinned_drop(&mut self) {
78 let p = unsafe { Pin::new_unchecked(self) };
79 p.drop()
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate as const_field_offset;
87
88 #[derive(Debug, FieldOffsets)]
89 #[repr(C)]
90 struct Foo {
91 a: u32,
92 b: f64,
93 c: bool,
94 }
95
96 #[derive(Debug, FieldOffsets)]
97 #[repr(C)]
98 struct Bar {
99 x: u32,
100 y: Foo,
101 }
102
103 #[test]
104 #[allow(clippy::float_cmp)]
105 fn test_simple() {
106 let foo_b = Foo::FIELD_OFFSETS.b();
107 let mut x = Foo { a: 1, b: 2.0, c: false };
108 assert_eq!(*foo_b.apply(&x), 2.0);
109 *foo_b.apply_mut(&mut x) = 42.0;
110 assert_eq!(x.b, 42.0);
111 }
112
113 #[test]
114 #[allow(clippy::float_cmp)]
115 fn test_nested() {
116 let mut x = Bar { x: 0, y: Foo { a: 1, b: 2.0, c: false } };
117 let bar_y_b = Bar::FIELD_OFFSETS.y() + Foo::FIELD_OFFSETS.b();
118 *bar_y_b.apply_mut(&mut x) = 42.0;
119 assert_eq!(x.y.b, 42.0);
120 }
121
122 #[test]
123 #[allow(clippy::float_cmp)]
124 fn test_pin() {
125 use alloc::boxed::Box;
126 let foo_b = Foo::FIELD_OFFSETS.b();
127 let foo_b_pin = unsafe { foo_b.as_pinned_projection() };
128 let foo_object = Box::pin(Foo { a: 21, b: 22.0, c: true });
129 let pb: Pin<&f64> = foo_b_pin.apply_pin(foo_object.as_ref());
130 assert_eq!(*pb, 22.0);
131
132 let mut x = Box::pin(Bar { x: 0, y: Foo { a: 1, b: 52.0, c: false } });
133 let bar_y_b = Bar::FIELD_OFFSETS.y() + foo_b_pin;
134 assert_eq!(*bar_y_b.apply(&*x), 52.0);
135
136 let bar_y_pin = unsafe { Bar::FIELD_OFFSETS.y().as_pinned_projection() };
137 *(bar_y_pin + foo_b_pin).apply_pin_mut(x.as_mut()) = 12.;
138 assert_eq!(x.y.b, 12.0);
139 }
140
141 // Verify FIELD_OFFSETS is const-evaluable
142 const _CONST_FOO_B: FieldOffset<Foo, f64> = Foo::FIELD_OFFSETS.b();
143 const _CONST_BAR_Y: FieldOffset<Bar, Foo> = Bar::FIELD_OFFSETS.y();
144}
145
146/**
147Test that one can't implement Unpin for pinned struct
148
149This should work:
150
151```rust
152use const_field_offset::FieldOffsets;
153#[repr(C)]
154#[derive(FieldOffsets)]
155struct Foo {
156 x: u32,
157}
158impl Unpin for Foo {}
159```
160
161But not this:
162```compile_fail
163use const_field_offset::FieldOffsets;
164#[repr(C)]
165#[derive(FieldOffsets)]
166#[pin]
167struct Foo {
168 x: u32,
169}
170impl Unpin for Foo {}
171```
172*/
173#[cfg(doctest)]
174const NO_IMPL_UNPIN: u32 = 0;