1//! Efficient decimal integer formatting.
2//!
3//! # Safety
4//!
5//! This uses `CStr::from_bytes_with_nul_unchecked` and
6//! `str::from_utf8_unchecked`on the buffer that it filled itself.
7#![allow(unsafe_code)]
8
9use crate::backend::fd::{AsFd, AsRawFd as _};
10use crate::ffi::CStr;
11use core::fmt;
12use core::hint::unreachable_unchecked;
13use core::mem::{self, MaybeUninit};
14use core::num::{NonZeroU8, NonZeroUsize};
15#[cfg(all(feature = "std", unix))]
16use std::os::unix::ffi::OsStrExt;
17#[cfg(all(
18 feature = "std",
19 target_os = "wasi",
20 any(not(target_env = "p2"), wasip2)
21))]
22use std::os::wasi::ffi::OsStrExt;
23#[cfg(feature = "std")]
24use {std::ffi::OsStr, std::path::Path};
25
26/// Format an integer into a decimal `Path` component, without constructing a
27/// temporary `PathBuf` or `String`.
28///
29/// This is used for opening paths such as `/proc/self/fd/<fd>` on Linux.
30///
31/// # Examples
32///
33/// ```
34/// # #[cfg(any(feature = "fs", feature = "net"))]
35/// use rustix::path::DecInt;
36///
37/// # #[cfg(any(feature = "fs", feature = "net"))]
38/// assert_eq!(
39/// format!("hello {}", DecInt::new(9876).as_ref().display()),
40/// "hello 9876"
41/// );
42/// ```
43#[derive(Clone)]
44pub struct DecInt {
45 buf: [MaybeUninit<u8>; BUF_LEN],
46 len: NonZeroU8,
47}
48
49/// Enough to hold an {u,i}64 and NUL terminator.
50const BUF_LEN: usize = U64_MAX_STR_LEN + 1;
51
52/// Maximum length of a formatted [`u64`].
53const U64_MAX_STR_LEN: usize = "18446744073709551615".len();
54
55/// Maximum length of a formatted [`i64`].
56#[allow(dead_code)]
57const I64_MAX_STR_LEN: usize = "-9223372036854775808".len();
58
59const _: () = assert!(U64_MAX_STR_LEN == I64_MAX_STR_LEN);
60
61mod private {
62 pub trait Sealed: Copy {
63 type Unsigned: super::Integer;
64
65 fn as_unsigned(self) -> (bool, Self::Unsigned);
66 fn eq_zero(self) -> bool;
67 fn div_mod_10(&mut self) -> u8;
68 }
69
70 macro_rules! impl_unsigned {
71 ($($ty:ty)+) => { $(
72 impl Sealed for $ty {
73 type Unsigned = $ty;
74
75 #[inline]
76 fn as_unsigned(self) -> (bool, $ty) {
77 (false, self)
78 }
79
80 #[inline]
81 fn eq_zero(self) -> bool {
82 self == 0
83 }
84
85 #[inline]
86 fn div_mod_10(&mut self) -> u8 {
87 let result = (*self % 10) as u8;
88 *self /= 10;
89 result
90 }
91 }
92 )+ }
93 }
94
95 macro_rules! impl_signed {
96 ($($signed:ty : $unsigned:ty)+) => { $(
97 impl Sealed for $signed {
98 type Unsigned = $unsigned;
99
100 #[inline]
101 fn as_unsigned(self) -> (bool, $unsigned) {
102 if self >= 0 {
103 (false, self as $unsigned)
104 } else {
105 (true, !(self as $unsigned) + 1)
106 }
107 }
108
109 #[inline]
110 fn eq_zero(self) -> bool {
111 unimplemented!()
112 }
113
114 #[inline]
115 fn div_mod_10(&mut self) -> u8 {
116 unimplemented!()
117 }
118 }
119 )+ }
120 }
121
122 impl_unsigned!(u8 u16 u32 u64);
123 impl_signed!(i8:u8 i16:u16 i32:u32 i64:u64);
124
125 #[cfg(any(
126 target_pointer_width = "16",
127 target_pointer_width = "32",
128 target_pointer_width = "64"
129 ))]
130 const _: () = {
131 impl_unsigned!(usize);
132 impl_signed!(isize:usize);
133 };
134}
135
136/// An integer that can be used by [`DecInt::new`].
137pub trait Integer: private::Sealed {}
138
139impl Integer for i8 {}
140impl Integer for i16 {}
141impl Integer for i32 {}
142impl Integer for i64 {}
143impl Integer for u8 {}
144impl Integer for u16 {}
145impl Integer for u32 {}
146impl Integer for u64 {}
147
148#[cfg(any(
149 target_pointer_width = "16",
150 target_pointer_width = "32",
151 target_pointer_width = "64"
152))]
153const _: () = {
154 impl Integer for isize {}
155 impl Integer for usize {}
156};
157
158impl DecInt {
159 /// Construct a new path component from an integer.
160 pub fn new<Int: Integer>(i: Int) -> Self {
161 use private::Sealed as _;
162
163 let (is_neg, mut i) = i.as_unsigned();
164 let mut len = 1;
165 let mut buf = [MaybeUninit::uninit(); BUF_LEN];
166 buf[BUF_LEN - 1] = MaybeUninit::new(b'\0');
167
168 // We use `loop { …; if cond { break } }` instead of
169 // `while !cond { … }` so the loop is entered at least once. This way
170 // `0` does not need a special handling.
171 loop {
172 len += 1;
173 if len > BUF_LEN {
174 // SAFETY: A stringified `i64`/`u64` cannot be longer than
175 // `U64_MAX_STR_LEN` bytes.
176 unsafe { unreachable_unchecked() };
177 }
178 buf[BUF_LEN - len] = MaybeUninit::new(b'0' + i.div_mod_10());
179 if i.eq_zero() {
180 break;
181 }
182 }
183
184 if is_neg {
185 len += 1;
186 if len > BUF_LEN {
187 // SAFETY: A stringified `i64`/`u64` cannot be longer than
188 // `U64_MAX_STR_LEN` bytes.
189 unsafe { unreachable_unchecked() };
190 }
191 buf[BUF_LEN - len] = MaybeUninit::new(b'-');
192 }
193
194 Self {
195 buf,
196 len: NonZeroU8::new(len as u8).unwrap(),
197 }
198 }
199
200 /// Construct a new path component from a file descriptor.
201 #[inline]
202 pub fn from_fd<Fd: AsFd>(fd: Fd) -> Self {
203 Self::new(fd.as_fd().as_raw_fd())
204 }
205
206 /// Return the raw byte buffer as a `&str`.
207 #[inline]
208 pub fn as_str(&self) -> &str {
209 // SAFETY: `DecInt` always holds a formatted decimal number, so it's
210 // always valid UTF-8.
211 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
212 }
213
214 /// Return the raw byte buffer as a `&CStr`.
215 #[inline]
216 pub fn as_c_str(&self) -> &CStr {
217 let bytes_with_nul = self.as_bytes_with_nul();
218 debug_assert!(CStr::from_bytes_with_nul(bytes_with_nul).is_ok());
219
220 // SAFETY: `self.buf` holds a single decimal ASCII representation and
221 // at least one extra NUL byte.
222 unsafe { CStr::from_bytes_with_nul_unchecked(bytes_with_nul) }
223 }
224
225 /// Return the raw byte buffer including the NUL byte.
226 #[inline]
227 pub fn as_bytes_with_nul(&self) -> &[u8] {
228 let len = NonZeroUsize::from(self.len).get();
229 if len > BUF_LEN {
230 // SAFETY: A stringified `i64`/`u64` cannot be longer than
231 // `U64_MAX_STR_LEN` bytes.
232 unsafe { unreachable_unchecked() };
233 }
234 let init = &self.buf[(self.buf.len() - len)..];
235 // SAFETY: We're guaranteed to have initialized `len + 1` bytes.
236 unsafe { mem::transmute::<&[MaybeUninit<u8>], &[u8]>(init) }
237 }
238
239 /// Return the raw byte buffer.
240 #[inline]
241 pub fn as_bytes(&self) -> &[u8] {
242 let bytes = self.as_bytes_with_nul();
243 &bytes[..bytes.len() - 1]
244 }
245}
246
247#[cfg(feature = "std")]
248#[cfg(any(not(target_os = "wasi"), not(target_env = "p2"), wasip2))]
249impl AsRef<Path> for DecInt {
250 #[inline]
251 fn as_ref(&self) -> &Path {
252 let as_os_str: &OsStr = OsStrExt::from_bytes(self.as_bytes());
253 Path::new(as_os_str)
254 }
255}
256
257impl fmt::Debug for DecInt {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 self.as_str().fmt(f)
260 }
261}
262