osom_primitives/
offset.rs

1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
2use crate::length::Length;
3
4/// Represents offset internally used by osom tools. This is similar
5/// to [`Length`][`crate::length::Length`], and internally is represented
6/// as a 32-bit signed integer. The point is that [`Offset`] can be added
7/// and removed from [`Length`][`crate::length::Length`].
8#[repr(transparent)]
9#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
10#[must_use]
11pub struct Offset {
12    value: i32,
13}
14
15/// Represents possible [`Offset`] errors.
16#[repr(C)]
17#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18pub enum OffsetError {
19    /// [`Offset`] is bigger than [`Offset::MAX_OFFSET`].
20    AboveMaxRange = 0,
21
22    /// [`Offset`] is smaller than [`Offset::MIN_OFFSET`].
23    BelowMinRange = 1,
24}
25
26impl Offset {
27    const SAFE_MARGIN: i32 = const {
28        assert!(Length::SAFE_MARGIN < i32::MAX as u32);
29        Length::SAFE_MARGIN as i32
30    };
31
32    /// The maximal value [`Offset`] can take.
33    pub const MAX_OFFSET: Offset = const {
34        assert!(Self::SAFE_MARGIN >= 64, "SAFE_MARGIN has to be at least 64.");
35        assert!(
36            Self::SAFE_MARGIN < i32::MAX,
37            "SAFE_MARGIN has to be smaller than i32::MAX"
38        );
39        unsafe { Offset::new_unchecked(i32::MAX - Self::SAFE_MARGIN) }
40    };
41
42    /// The minimal value [`Offset`] can take.
43    pub const MIN_OFFSET: Offset = const {
44        assert!(Self::SAFE_MARGIN >= 64, "SAFE_MARGIN has to be at least 64.");
45        assert!(
46            Self::SAFE_MARGIN < i32::MAX,
47            "SAFE_MARGIN has to be smaller than i32::MAX"
48        );
49        unsafe { Offset::new_unchecked(i32::MIN + Self::SAFE_MARGIN) }
50    };
51
52    /// Represents [`Offset`] zero.
53    pub const ZERO: Self = unsafe { Self::new_unchecked(0) };
54
55    /// Represents [`Offset`] one.
56    pub const ONE: Self = unsafe { Self::new_unchecked(1) };
57
58    /// Represents [`Offset`] minus one.
59    pub const MINUS_ONE: Self = unsafe { Self::new_unchecked(-1) };
60
61    /// Creates a new [`Offset`] out of `i32`.
62    ///
63    /// # Safety
64    ///
65    /// This function does not validate `value`. It is up to the
66    /// caller to ensure that its value is between [`Offset::MIN_OFFSET`]
67    /// and [`Offset::MAX_OFFSET`].
68    #[inline(always)]
69    pub const unsafe fn new_unchecked(value: i32) -> Self {
70        Self { value }
71    }
72
73    /// Creates a new [`Offset`] from `i32`.
74    ///
75    /// # Errors
76    ///
77    /// For details see [`OffsetError`].
78    #[inline(always)]
79    pub const fn try_from_i32(value: i32) -> Result<Self, OffsetError> {
80        if value < Self::MIN_OFFSET.as_i32() {
81            Err(OffsetError::BelowMinRange)
82        } else if value > Self::MAX_OFFSET.as_i32() {
83            Err(OffsetError::AboveMaxRange)
84        } else {
85            Ok(unsafe { Self::new_unchecked(value) })
86        }
87    }
88
89    /// Creates a new [`Offset`] from `u32`.
90    ///
91    /// # Errors
92    ///
93    /// For details see [`OffsetError`].
94    #[inline(always)]
95    pub const fn try_from_u32(value: u32) -> Result<Self, OffsetError> {
96        if value > Self::MAX_OFFSET.as_i32() as u32 {
97            return Err(OffsetError::AboveMaxRange);
98        }
99
100        let value = value as i32;
101
102        Self::try_from_i32(value)
103    }
104
105    /// Turns the [`Offset`] into `i32`.
106    #[inline(always)]
107    #[must_use]
108    pub const fn as_i32(&self) -> i32 {
109        self.value
110    }
111
112    /// Turns the [`Offset`] into `isize`.
113    #[inline(always)]
114    #[must_use]
115    pub const fn as_isize(&self) -> isize {
116        self.value as isize
117    }
118}
119
120impl TryFrom<i32> for Offset {
121    type Error = OffsetError;
122
123    fn try_from(value: i32) -> Result<Self, Self::Error> {
124        Self::try_from_i32(value)
125    }
126}
127
128impl TryFrom<u32> for Offset {
129    type Error = OffsetError;
130
131    fn try_from(value: u32) -> Result<Self, Self::Error> {
132        Self::try_from_u32(value)
133    }
134}
135
136impl TryFrom<isize> for Offset {
137    type Error = OffsetError;
138
139    fn try_from(value: isize) -> Result<Self, Self::Error> {
140        if value > Offset::MAX_OFFSET.as_isize() {
141            Err(OffsetError::AboveMaxRange)
142        } else if value < Offset::MIN_OFFSET.as_isize() {
143            Err(OffsetError::BelowMinRange)
144        } else {
145            let value = value as i32;
146
147            Ok(unsafe { Self::new_unchecked(value) })
148        }
149    }
150}
151
152impl From<Offset> for i32 {
153    fn from(value: Offset) -> Self {
154        value.as_i32()
155    }
156}
157
158impl From<Offset> for isize {
159    fn from(value: Offset) -> Self {
160        value.as_isize()
161    }
162}
163
164impl core::fmt::Display for Offset {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        self.as_i32().fmt(f)
167    }
168}
169
170const _: () = const {
171    assert!(
172        size_of::<Offset>() == size_of::<Length>(),
173        "Offset and length have to be of the same size"
174    );
175    assert!(
176        align_of::<Offset>() == align_of::<Length>(),
177        "Offset and length have to have the same alignment"
178    );
179};