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/// This trait needs to be implemented if you use the `#[pin_drop]` attribute. It enables
57/// you to implement Drop for your type safely.
58pub trait PinnedDrop {
59 /// This is the equivalent to the regular Drop trait with the difference that self
60 /// is pinned.
61 fn drop(self: Pin<&mut Self>);
62
63 #[doc(hidden)]
64 fn do_safe_pinned_drop(&mut self) {
65 let p = unsafe { Pin::new_unchecked(self) };
66 p.drop()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate as const_field_offset;
74
75 #[derive(Debug, FieldOffsets)]
76 #[repr(C)]
77 struct Foo {
78 a: u32,
79 b: f64,
80 c: bool,
81 }
82
83 #[derive(Debug, FieldOffsets)]
84 #[repr(C)]
85 struct Bar {
86 x: u32,
87 y: Foo,
88 }
89
90 #[test]
91 #[allow(clippy::float_cmp)]
92 fn test_simple() {
93 let foo_b = Foo::FIELD_OFFSETS.b();
94 let mut x = Foo { a: 1, b: 2.0, c: false };
95 assert_eq!(*foo_b.apply(&x), 2.0);
96 *foo_b.apply_mut(&mut x) = 42.0;
97 assert_eq!(x.b, 42.0);
98 }
99
100 #[test]
101 #[allow(clippy::float_cmp)]
102 fn test_nested() {
103 let mut x = Bar { x: 0, y: Foo { a: 1, b: 2.0, c: false } };
104 let bar_y_b = Bar::FIELD_OFFSETS.y() + Foo::FIELD_OFFSETS.b();
105 *bar_y_b.apply_mut(&mut x) = 42.0;
106 assert_eq!(x.y.b, 42.0);
107 }
108
109 #[test]
110 #[allow(clippy::float_cmp)]
111 fn test_pin() {
112 use alloc::boxed::Box;
113 let foo_b = Foo::FIELD_OFFSETS.b();
114 let foo_b_pin = unsafe { foo_b.as_pinned_projection() };
115 let foo_object = Box::pin(Foo { a: 21, b: 22.0, c: true });
116 let pb: Pin<&f64> = foo_b_pin.apply_pin(foo_object.as_ref());
117 assert_eq!(*pb, 22.0);
118
119 let mut x = Box::pin(Bar { x: 0, y: Foo { a: 1, b: 52.0, c: false } });
120 let bar_y_b = Bar::FIELD_OFFSETS.y() + foo_b_pin;
121 assert_eq!(*bar_y_b.apply(&*x), 52.0);
122
123 let bar_y_pin = unsafe { Bar::FIELD_OFFSETS.y().as_pinned_projection() };
124 *(bar_y_pin + foo_b_pin).apply_pin_mut(x.as_mut()) = 12.;
125 assert_eq!(x.y.b, 12.0);
126 }
127
128 // Verify FIELD_OFFSETS is const-evaluable
129 const _CONST_FOO_B: FieldOffset<Foo, f64> = Foo::FIELD_OFFSETS.b();
130 const _CONST_BAR_Y: FieldOffset<Bar, Foo> = Bar::FIELD_OFFSETS.y();
131}
132
133/**
134Test that one can't implement Unpin for pinned struct
135
136This should work:
137
138```rust
139use const_field_offset::FieldOffsets;
140#[repr(C)]
141#[derive(FieldOffsets)]
142struct Foo {
143 x: u32,
144}
145impl Unpin for Foo {}
146```
147
148But not this:
149```compile_fail
150use const_field_offset::FieldOffsets;
151#[repr(C)]
152#[derive(FieldOffsets)]
153#[pin]
154struct Foo {
155 x: u32,
156}
157impl Unpin for Foo {}
158```
159*/
160#[cfg(doctest)]
161const NO_IMPL_UNPIN: u32 = 0;