dirbuf/
lib.rs

1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2
3#![cfg_attr(feature = "nightly-safe-impl", feature(os_string_truncate))]
4
5#![warn(
6    clippy::pedantic,
7    clippy::undocumented_unsafe_blocks,
8    clippy::unnecessary_safety_doc,
9)]
10#![allow(clippy::inline_always)]
11
12use std::path::{Path, PathBuf};
13use std::ffi::OsString;
14
15mod inner;
16
17pub use inner::{DirBuf, DirBufEntry};
18
19macro_rules! impl_common {
20    ($($x:tt)+) => {
21        impl $($x)+ {
22            /// Efficiently join a path with another.
23            ///
24            /// The returned [`DirBufEntry`] reuses the underlying buffer,
25            /// and must be dropped before `join` can be called again.
26            /// The `DirBufEntry` holds a mutable reference to ensure
27            /// `join` cannot be called before the `DirBufEntry` is dropped.
28            ///
29            /// If the path segment is coming from untrusted user data and
30            /// panics are undesirable, use [`try_join`](Self::try_join) instead.
31            ///
32            /// # Panics
33            /// Panics if `path` is not a [trivial path](crate#trivial-paths).
34            ///
35            /// # Performance
36            /// The overhead of the panic check is not insubstantial.
37            /// When multiple `join`s are used, this is still usually
38            /// faster than using a `PathBuf`, but if you know
39            /// the path is trivial (e.g. if it is a string literal),
40            /// consider using [`join_unchecked`](Self::join_unchecked) to remove this overhead.
41            ///
42            #[inline]
43            #[must_use]
44            pub fn join(&mut self, path: impl AsRef<Path>) -> DirBufEntry<'_> {
45                match self.try_join_inner(path.as_ref()) {
46                    Err(err) => panic!("unable to join paths: {err}"),
47                    Ok(entry) => entry,
48                }
49            }
50
51            /// Variant of `join` optimized for string literals.
52            ///
53            /// Semantic behavior is identical to [`Self::join`],
54            /// the only difference is performance.
55            ///
56            /// For simple cases, this can have performance equivalent to
57            /// that of [`Self::join_unchecked`] without the unsafety,
58            /// but for more complex cases,
59            /// [`Self::join`] may be more performant.
60            ///
61            /// It achieves this performance gain by leveraging inlining and
62            /// constant folding.
63            #[inline(always)]
64            #[must_use]
65            pub fn join_lit(&mut self, path: &str) -> DirBufEntry<'_> {
66                #[cfg(unix)]
67                {
68                    if trivially_trivial(path) {
69                        // SAFETY: just checked if it is trivial
70                        return unsafe { self.join_inner(path.as_ref()) };
71                    }
72                }
73                self.join(path)
74            }
75
76            /// Attempt to join a path with another.
77            ///
78            /// The returned [`DirBufEntry`] reuses the underlying buffer,
79            /// and must be dropped before `join` can be called again.
80            /// The `DirBufEntry` holds a mutable reference to ensure
81            /// `join` cannot be called before the `DirBufEntry` is dropped.
82            ///
83            /// # Errors
84            ///
85            /// An error is returned if the path is not [trivial](crate#trivial-paths).
86            #[inline]
87            pub fn try_join(&mut self, path: impl AsRef<Path>) -> Result<DirBufEntry<'_>, JoinError> {
88                self.try_join_inner(path.as_ref())
89            }
90
91            /// Join a path to another without checking that it is trivial.
92            ///
93            /// # Safety
94            /// `path` must be a [trivial path](crate#trivial-paths).
95            #[inline]
96            #[must_use]
97            pub unsafe fn join_unchecked(&mut self, path: impl AsRef<Path>) -> DirBufEntry<'_> {
98                // SAFETY: invariant guranteed by caller
99                unsafe { self.join_inner(path.as_ref()) }
100            }
101
102            #[inline]
103            fn try_join_inner(&mut self, path: &Path) -> Result<DirBufEntry<'_>, JoinError> {
104                check_trivial(path)?;
105                // SAFETY: we just checked if the path is trivial
106                unsafe { Ok(self.join_inner(path)) }
107            }
108
109            /// # Safety
110            /// The path must be a [trivial path](crate#trivial-paths).
111            #[must_use]
112            unsafe fn join_inner(&mut self, path: &Path) -> DirBufEntry<'_> {
113                
114                // we call this before each operation instead of when `DirBufEntry` is dropped
115                // to make sure it is leak-safe.
116                // SAFETY: the path must be trivial by this function's precondition
117                unsafe {self.push(path)};
118                self.as_entry()
119            }
120
121            /// Borrow as a [`Path`].
122            pub fn as_path(&self) -> &Path {
123                
124                self.as_ref()
125            }
126        }
127    }
128}
129
130impl DirBuf {
131    /// Initialize an empty `DirBuf` with the given capacity.
132    #[must_use]
133    pub fn with_capacity(capacity: usize) -> Self {
134        let buf = PathBuf::with_capacity(capacity);
135        DirBuf::new(buf)
136    }
137
138    /// Return the internal buffer, resetting it to its initial state first.
139    #[must_use]
140    pub fn into_inner(mut self) -> PathBuf {
141        let mut r = PathBuf::new();
142        // SAFETY: we consume the `DirBuf` so no further operations can be performed on it.
143        std::mem::swap(unsafe { self.reset() }, &mut r);
144        r
145    }
146}
147
148
149impl<T: AsRef<Path>> PartialEq<T> for DirBuf {
150    fn eq(&self, other: &T) -> bool {
151        self.as_ref() == other.as_ref()
152    }
153}
154
155impl<T: AsRef<Path>> PartialEq<T> for DirBufEntry<'_> {
156    fn eq(&self, other: &T) -> bool {
157        self.as_ref() == other.as_ref()
158    }
159}
160
161impl_common!(DirBuf);
162impl_common!(<'buf> DirBufEntry<'buf>);
163
164/// An error that occurred while attempting to join two paths.
165#[derive(thiserror::Error, Debug, Clone)]
166#[error("path {path:?} is not trivial due to {component:?}")]
167pub struct JoinError {
168    path: PathBuf,
169    component: OsString,
170}
171
172#[cold]
173fn cold<T>(x: T) -> T {
174    x
175}
176
177#[inline(always)]
178const fn trivially_trivial(s: &str) -> bool {
179    let mut i = 0;
180    let bytes = s.as_bytes();
181    if bytes.is_empty() { return true; }
182    if bytes[0] == b'/' { return false; }
183    while i < bytes.len() {
184        if bytes[i] == b'.' {
185            return false;
186        }
187        i += 1;
188    }
189    true
190}
191
192#[inline]
193fn check_trivial(path: &Path) -> Result<(), JoinError> {
194    use std::path::Component as Comp;
195    for comp in path.components() {
196        match &comp {
197            Comp::CurDir | Comp::Normal(..) => {
198                // ok, do nothing
199            },
200            _ => return cold(Err(JoinError{
201                path: path.into(),
202                component: comp.as_os_str().to_owned(),
203            })),
204        }
205    }
206    Ok(())
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn it_works() {
215        let mut d = DirBuf::new("foo");
216        assert_eq!(d.join("bar"), Path::new("foo/bar"));
217        assert_eq!(d, "foo");
218    }
219}