rustix/path/
dec_int.rs

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};
10use crate::ffi::CStr;
11use core::mem::{self, MaybeUninit};
12use itoa::{Buffer, Integer};
13#[cfg(all(feature = "std", unix))]
14use std::os::unix::ffi::OsStrExt;
15#[cfg(all(feature = "std", target_os = "wasi"))]
16use std::os::wasi::ffi::OsStrExt;
17#[cfg(feature = "std")]
18use {core::fmt, std::ffi::OsStr, std::path::Path};
19
20/// Format an integer into a decimal `Path` component, without constructing a
21/// temporary `PathBuf` or `String`.
22///
23/// This is used for opening paths such as `/proc/self/fd/<fd>` on Linux.
24///
25/// # Examples
26///
27/// ```
28/// # #[cfg(any(feature = "fs", feature = "net"))]
29/// use rustix::path::DecInt;
30///
31/// # #[cfg(any(feature = "fs", feature = "net"))]
32/// assert_eq!(
33///     format!("hello {}", DecInt::new(9876).as_ref().display()),
34///     "hello 9876"
35/// );
36/// ```
37#[derive(Clone)]
38pub struct DecInt {
39    // Enough to hold an {u,i}64 and NUL terminator.
40    buf: [MaybeUninit<u8>; u64::MAX_STR_LEN + 1],
41    len: usize,
42}
43const _: () = assert!(u64::MAX_STR_LEN == i64::MAX_STR_LEN);
44
45impl DecInt {
46    /// Construct a new path component from an integer.
47    #[inline]
48    pub fn new<Int: Integer>(i: Int) -> Self {
49        let mut buf = [MaybeUninit::uninit(); 21];
50
51        let mut str_buf = Buffer::new();
52        let str_buf = str_buf.format(i);
53        assert!(
54            str_buf.len() < buf.len(),
55            "{str_buf}{} unsupported.",
56            core::any::type_name::<Int>()
57        );
58
59        buf[..str_buf.len()].copy_from_slice(unsafe {
60            // SAFETY: you can always go from init to uninit
61            mem::transmute::<&[u8], &[MaybeUninit<u8>]>(str_buf.as_bytes())
62        });
63        buf[str_buf.len()] = MaybeUninit::new(0);
64
65        Self {
66            buf,
67            len: str_buf.len(),
68        }
69    }
70
71    /// Construct a new path component from a file descriptor.
72    #[inline]
73    pub fn from_fd<Fd: AsFd>(fd: Fd) -> Self {
74        Self::new(fd.as_fd().as_raw_fd())
75    }
76
77    /// Return the raw byte buffer as a `&str`.
78    #[inline]
79    pub fn as_str(&self) -> &str {
80        // SAFETY: `DecInt` always holds a formatted decimal number, so it's
81        // always valid UTF-8.
82        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
83    }
84
85    /// Return the raw byte buffer as a `&CStr`.
86    #[inline]
87    pub fn as_c_str(&self) -> &CStr {
88        let bytes_with_nul = self.as_bytes_with_nul();
89        debug_assert!(CStr::from_bytes_with_nul(bytes_with_nul).is_ok());
90
91        // SAFETY: `self.buf` holds a single decimal ASCII representation and
92        // at least one extra NUL byte.
93        unsafe { CStr::from_bytes_with_nul_unchecked(bytes_with_nul) }
94    }
95
96    /// Return the raw byte buffer including the NUL byte.
97    #[inline]
98    pub fn as_bytes_with_nul(&self) -> &[u8] {
99        let init = &self.buf[..=self.len];
100        // SAFETY: we're guaranteed to have initialized len+1 bytes.
101        unsafe { mem::transmute::<&[MaybeUninit<u8>], &[u8]>(init) }
102    }
103
104    /// Return the raw byte buffer.
105    #[inline]
106    pub fn as_bytes(&self) -> &[u8] {
107        let bytes = self.as_bytes_with_nul();
108        &bytes[..bytes.len() - 1]
109    }
110}
111
112#[cfg(feature = "std")]
113impl AsRef<Path> for DecInt {
114    #[inline]
115    fn as_ref(&self) -> &Path {
116        let as_os_str: &OsStr = OsStrExt::from_bytes(self.as_bytes());
117        Path::new(as_os_str)
118    }
119}
120
121#[cfg(feature = "std")]
122impl fmt::Debug for DecInt {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        self.as_str().fmt(f)
125    }
126}