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 expose the [`FieldOffsets`] derive macro and the types it uses.
6
7The macro allows to get const FieldOffset for member of a `#[repr(C)]` struct.
8
9The [`FieldOffset`] type is re-exported from the `field-offset` crate.
10*/
11#![no_std]
12
13#[cfg(test)]
14extern crate alloc;
15
16use core::pin::Pin;
17
18#[doc(inline)]
19pub use const_field_offset_macro::FieldOffsets;
20
21pub use field_offset::{AllowPin, FieldOffset, NotPinned};
22
23/// This trait needs to be implemented if you use the `#[pin_drop]` attribute. It enables
24/// you to implement Drop for your type safely.
25pub trait PinnedDrop {
26    /// This is the equivalent to the regular Drop trait with the difference that self
27    /// is pinned.
28    fn drop(self: Pin<&mut Self>);
29
30    #[doc(hidden)]
31    fn do_safe_pinned_drop(&mut self) {
32        let p = unsafe { Pin::new_unchecked(self) };
33        p.drop()
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate as const_field_offset;
41    // ### Structures were change to repr(c) and to inherit FieldOffsets
42
43    // Example structures
44    #[derive(Debug, FieldOffsets)]
45    #[repr(C)]
46    struct Foo {
47        a: u32,
48        b: f64,
49        c: bool,
50    }
51
52    #[derive(Debug, FieldOffsets)]
53    #[repr(C)]
54    struct Bar {
55        x: u32,
56        y: Foo,
57    }
58
59    #[test]
60    #[allow(clippy::float_cmp)] // We want bit-wise equality here
61    fn test_simple() {
62        // Get a pointer to `b` within `Foo`
63        let foo_b = Foo::FIELD_OFFSETS.b;
64
65        // Construct an example `Foo`
66        let mut x = Foo { a: 1, b: 2.0, c: false };
67
68        // Apply the pointer to get at `b` and read it
69        {
70            let y = foo_b.apply(&x);
71            assert_eq!(*y, 2.0);
72        }
73
74        // Apply the pointer to get at `b` and mutate it
75        {
76            let y = foo_b.apply_mut(&mut x);
77            *y = 42.0;
78        }
79        assert_eq!(x.b, 42.0);
80    }
81
82    #[test]
83    #[allow(clippy::float_cmp)] // We want bit-wise equality here
84    fn test_nested() {
85        // Construct an example `Foo`
86        let mut x = Bar { x: 0, y: Foo { a: 1, b: 2.0, c: false } };
87
88        // Combine the pointer-to-members
89        let bar_y_b = Bar::FIELD_OFFSETS.y + Foo::FIELD_OFFSETS.b;
90
91        // Apply the pointer to get at `b` and mutate it
92        {
93            let y = bar_y_b.apply_mut(&mut x);
94            *y = 42.0;
95        }
96        assert_eq!(x.y.b, 42.0);
97    }
98
99    #[test]
100    #[allow(clippy::float_cmp)] // We want bit-wise equality here
101    fn test_pin() {
102        use ::alloc::boxed::Box;
103        // Get a pointer to `b` within `Foo`
104        let foo_b = Foo::FIELD_OFFSETS.b;
105        let foo_b_pin = unsafe { foo_b.as_pinned_projection() };
106        let foo_object = Box::pin(Foo { a: 21, b: 22.0, c: true });
107        let pb: Pin<&f64> = foo_b_pin.apply_pin(foo_object.as_ref());
108        assert_eq!(*pb, 22.0);
109
110        let mut x = Box::pin(Bar { x: 0, y: Foo { a: 1, b: 52.0, c: false } });
111        let bar_y_b = Bar::FIELD_OFFSETS.y + foo_b_pin;
112        assert_eq!(*bar_y_b.apply(&*x), 52.0);
113
114        let bar_y_pin = unsafe { Bar::FIELD_OFFSETS.y.as_pinned_projection() };
115        *(bar_y_pin + foo_b_pin).apply_pin_mut(x.as_mut()) = 12.;
116        assert_eq!(x.y.b, 12.0);
117    }
118}
119
120/**
121Test that one can't implement Unpin for pinned struct
122
123This should work:
124
125```
126#[derive(const_field_offset::FieldOffsets)]
127#[repr(C)]
128#[pin]
129struct MyStructPin { a: u32 }
130```
131
132But this not:
133
134```compile_fail
135#[derive(const_field_offset::FieldOffsets)]
136#[repr(C)]
137#[pin]
138struct MyStructPin { a: u32 }
139impl Unpin for MyStructPin {};
140```
141
142*/
143#[cfg(doctest)]
144const NO_IMPL_UNPIN: u32 = 0;
145
146#[doc(hidden)]
147#[cfg(feature = "field-offset-trait")]
148mod internal {
149    use super::*;
150    pub trait CombineFlag {
151        type Output;
152    }
153    impl CombineFlag for (AllowPin, AllowPin) {
154        type Output = AllowPin;
155    }
156    impl CombineFlag for (NotPinned, AllowPin) {
157        type Output = NotPinned;
158    }
159    impl CombineFlag for (AllowPin, NotPinned) {
160        type Output = NotPinned;
161    }
162    impl CombineFlag for (NotPinned, NotPinned) {
163        type Output = NotPinned;
164    }
165}
166
167#[cfg(feature = "field-offset-trait")]
168pub trait ConstFieldOffset: Copy {
169    /// The type of the container
170    type Container;
171    /// The type of the field
172    type Field;
173
174    /// Can be AllowPin or NotPinned
175    type PinFlag;
176
177    const OFFSET: FieldOffset<Self::Container, Self::Field, Self::PinFlag>;
178
179    fn as_field_offset(self) -> FieldOffset<Self::Container, Self::Field, Self::PinFlag> {
180        Self::OFFSET
181    }
182    fn get_byte_offset(self) -> usize {
183        Self::OFFSET.get_byte_offset()
184    }
185    fn apply(self, x: &Self::Container) -> &Self::Field {
186        Self::OFFSET.apply(x)
187    }
188    fn apply_mut(self, x: &mut Self::Container) -> &mut Self::Field {
189        Self::OFFSET.apply_mut(x)
190    }
191
192    fn apply_pin<'a>(self, x: Pin<&'a Self::Container>) -> Pin<&'a Self::Field>
193    where
194        Self: ConstFieldOffset<PinFlag = AllowPin>,
195    {
196        Self::OFFSET.apply_pin(x)
197    }
198    fn apply_pin_mut<'a>(self, x: Pin<&'a mut Self::Container>) -> Pin<&'a mut Self::Field>
199    where
200        Self: ConstFieldOffset<PinFlag = AllowPin>,
201    {
202        Self::OFFSET.apply_pin_mut(x)
203    }
204}
205
206/// This can be used to transmute a FieldOffset from a NotPinned to any pin flag.
207/// This is only valid if we know that the offset is actually valid for this Flag.
208#[cfg(feature = "field-offset-trait")]
209union TransmutePinFlag<Container, Field, PinFlag> {
210    x: FieldOffset<Container, Field, PinFlag>,
211    y: FieldOffset<Container, Field>,
212}
213
214/// Helper class used as the result of the addition of two types that implement the `ConstFieldOffset` trait
215#[derive(Copy, Clone)]
216#[cfg(feature = "field-offset-trait")]
217pub struct ConstFieldOffsetSum<A: ConstFieldOffset, B: ConstFieldOffset>(pub A, pub B);
218
219#[cfg(feature = "field-offset-trait")]
220impl<A: ConstFieldOffset, B: ConstFieldOffset> ConstFieldOffset for ConstFieldOffsetSum<A, B>
221where
222    A: ConstFieldOffset<Field = B::Container>,
223    (A::PinFlag, B::PinFlag): internal::CombineFlag,
224{
225    type Container = A::Container;
226    type Field = B::Field;
227    type PinFlag = <(A::PinFlag, B::PinFlag) as internal::CombineFlag>::Output;
228    const OFFSET: FieldOffset<Self::Container, Self::Field, Self::PinFlag> = unsafe {
229        TransmutePinFlag {
230            y: FieldOffset::new_from_offset(
231                A::OFFSET.get_byte_offset() + B::OFFSET.get_byte_offset(),
232            ),
233        }
234        .x
235    };
236}
237
238#[cfg(feature = "field-offset-trait")]
239impl<A: ConstFieldOffset, B: ConstFieldOffset, Other> ::core::ops::Add<Other>
240    for ConstFieldOffsetSum<A, B>
241where
242    Self: ConstFieldOffset,
243    Other: ConstFieldOffset<Container = <Self as ConstFieldOffset>::Field>,
244{
245    type Output = ConstFieldOffsetSum<Self, Other>;
246    fn add(self, other: Other) -> Self::Output {
247        ConstFieldOffsetSum(self, other)
248    }
249}