Skip to main content

typed_bytes/
lib.rs

1#![no_std]
2
3//! A `![no_std]`, const first Rust library for strongly-typed data size units (KiB, MiB, KB, MB, etc.).
4//!
5//! ## Features
6//!
7//! - `no_std`
8//! - `const`
9//!
10//! ## Usage
11//!
12//! This library provides both IEC and SI units.
13//!
14//! ### IEC Units (Base 2)
15//!
16//! ```rust
17//! use typed_bytes::iec::{KiB, MiB, GiB};
18//! use typed_bytes::Bytes;
19//!
20//! let size = KiB(5);
21//! let bytes: Bytes = size.into(); // 5 * 1024 = 5120 bytes
22//!
23//! assert_eq!(bytes, Bytes(5120));
24//! assert_eq!(MiB(1).as_kib(), KiB(1024));
25//! ```
26//!
27//! ### SI Units (Base 10)
28//!
29//! ```rust
30//! use typed_bytes::si::{KB, MB, GB};
31//! use typed_bytes::Bytes;
32//!
33//! let size = KB(5);
34//! let bytes: Bytes = size.into(); // 5 * 1000 = 5000 bytes
35//!
36//! assert_eq!(bytes, Bytes(5000));
37//! assert_eq!(MB(1).as_kb(), KB(1000));
38//! ```
39//!
40//! ## Comparisons & Safety
41//!
42//! To prevent "footguns" (accidental mixing of binary and decimal units), there are **no implicit comparisons** between SI and IEC units. You must strictly convert them to a common type (like `Bytes`) or explicitly convert one to the other before comparing.
43//!
44//! This compilation failure is a feature, not a bug:
45//!
46//! ```rust,compile_fail
47//! use typed_bytes::iec::KiB;
48//! use typed_bytes::si::KB;
49//!
50//! let kib = KiB(1);
51//! let kb = KB(1);
52//!
53//! // This fails to compile!
54//! if kib > kb {
55//!     println!("Mixed comparison");
56//! }
57//! ```
58//!
59//! This will compile:
60//!
61//! ```rust
62//! use typed_bytes::iec::KiB;
63//! use typed_bytes::si::KB;
64//! use typed_bytes::Bytes;
65//!
66//! let kib = KiB(1);
67//! let kb = KB(1);
68//!
69//! if Bytes::from(kib) > Bytes::from(kb) {
70//!     println!("1 KiB is greater than 1 KB");
71//! }
72//! ```
73
74use core::fmt;
75use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
76
77pub mod iec;
78pub mod si;
79
80use iec::{GIB, GiB, KIB, KiB, MIB, MiB, PIB, PiB, TIB, TiB};
81use si::{GB, GB_BYTES, KB, KB_BYTES, MB, MB_BYTES, PB, PB_BYTES, TB, TB_BYTES};
82
83/// Smallest unit of data storage
84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
85pub struct Bytes(pub u64);
86
87impl Bytes {
88    pub const fn new(val: u64) -> Self {
89        Self(val)
90    }
91
92    pub const fn as_u64(&self) -> u64 {
93        self.0
94    }
95
96    // IEC Conversions
97    pub const fn as_kib(self) -> KiB {
98        KiB(self.0 / KIB)
99    }
100
101    pub const fn as_mib(self) -> MiB {
102        MiB(self.0 / MIB)
103    }
104
105    pub const fn as_gib(self) -> GiB {
106        GiB(self.0 / GIB)
107    }
108
109    pub const fn as_tib(self) -> TiB {
110        TiB(self.0 / TIB)
111    }
112
113    pub const fn as_pib(self) -> PiB {
114        PiB(self.0 / PIB)
115    }
116
117    // SI Conversions
118    pub const fn as_kb(self) -> KB {
119        KB(self.0 / KB_BYTES)
120    }
121
122    pub const fn as_mb(self) -> MB {
123        MB(self.0 / MB_BYTES)
124    }
125
126    pub const fn as_gb(self) -> GB {
127        GB(self.0 / GB_BYTES)
128    }
129
130    pub const fn as_tb(self) -> TB {
131        TB(self.0 / TB_BYTES)
132    }
133
134    pub const fn as_pb(self) -> PB {
135        PB(self.0 / PB_BYTES)
136    }
137}
138
139impl fmt::Display for Bytes {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(f, "{} B", self.0)
142    }
143}
144
145impl Add for Bytes {
146    type Output = Self;
147    fn add(self, rhs: Self) -> Self::Output {
148        Self(self.0 + rhs.0)
149    }
150}
151impl AddAssign for Bytes {
152    fn add_assign(&mut self, rhs: Self) {
153        self.0 += rhs.0;
154    }
155}
156
157impl Sub for Bytes {
158    type Output = Self;
159    fn sub(self, rhs: Self) -> Self::Output {
160        Self(self.0 - rhs.0)
161    }
162}
163impl SubAssign for Bytes {
164    fn sub_assign(&mut self, rhs: Self) {
165        self.0 -= rhs.0;
166    }
167}
168
169impl Mul<u64> for Bytes {
170    type Output = Self;
171    fn mul(self, rhs: u64) -> Self::Output {
172        Self(self.0 * rhs)
173    }
174}
175impl Mul<Bytes> for u64 {
176    type Output = Bytes;
177    fn mul(self, rhs: Bytes) -> Self::Output {
178        Bytes(self * rhs.0)
179    }
180}
181impl MulAssign<u64> for Bytes {
182    fn mul_assign(&mut self, rhs: u64) {
183        self.0 *= rhs;
184    }
185}
186
187impl Div<u64> for Bytes {
188    type Output = Self;
189    fn div(self, rhs: u64) -> Self::Output {
190        Self(self.0 / rhs)
191    }
192}
193impl DivAssign<u64> for Bytes {
194    fn div_assign(&mut self, rhs: u64) {
195        self.0 /= rhs;
196    }
197}
198
199impl Div for Bytes {
200    type Output = u64;
201    fn div(self, rhs: Self) -> Self::Output {
202        self.0 / rhs.0
203    }
204}
205
206impl Rem for Bytes {
207    type Output = Self;
208    fn rem(self, rhs: Self) -> Self::Output {
209        Self(self.0 % rhs.0)
210    }
211}
212
213impl RemAssign for Bytes {
214    fn rem_assign(&mut self, rhs: Self) {
215        self.0 %= rhs.0;
216    }
217}
218
219// From conversions
220impl From<u64> for Bytes {
221    fn from(val: u64) -> Self {
222        Bytes(val)
223    }
224}
225
226impl From<usize> for Bytes {
227    fn from(val: usize) -> Self {
228        Bytes(val as u64)
229    }
230}
231
232impl From<Bytes> for u64 {
233    fn from(bytes: Bytes) -> u64 {
234        bytes.0
235    }
236}
237
238#[macro_export]
239macro_rules! impl_comparison {
240    ($target:ident, $($other:ident),+) => {
241        $(
242            impl PartialEq<$other> for $target {
243                fn eq(&self, other: &$other) -> bool {
244                    $crate::Bytes::from(*self) == $crate::Bytes::from(*other)
245                }
246            }
247
248            impl PartialEq<$target> for $other {
249                fn eq(&self, other: &$target) -> bool {
250                    $crate::Bytes::from(*self) == $crate::Bytes::from(*other)
251                }
252            }
253
254            impl PartialOrd<$other> for $target {
255                fn partial_cmp(&self, other: &$other) -> Option<core::cmp::Ordering> {
256                    $crate::Bytes::from(*self).partial_cmp(&$crate::Bytes::from(*other))
257                }
258            }
259
260            impl PartialOrd<$target> for $other {
261                fn partial_cmp(&self, other: &$target) -> Option<core::cmp::Ordering> {
262                    $crate::Bytes::from(*self).partial_cmp(&$crate::Bytes::from(*other))
263                }
264            }
265        )+
266    };
267}