Skip to main content

osom_lib_primitives/
offset.rs

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