maybe_path/
maybe_path.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use std::{
    ffi::OsStr,
    fmt::{Debug, Display},
    hash::Hash,
    ops::Deref,
    path::{Path, PathBuf},
};

/// Interior storage for `MaybePath`
union InnerMaybePath<'a> {
    path: &'a Path,
    str: &'a str,
}
impl Copy for InnerMaybePath<'_> {}
impl Clone for InnerMaybePath<'_> {
    #[inline]
    fn clone(&self) -> Self {
        *self
    }
}

/// A Near-Zero-Overhead read-only `Path` wrapper that can also hold a `str`.  
/// The primary usecase is static initialization of a `Path` at compile-time.
///
/// It implements `Deref<Target = Path>`, so you can treat it as a drop-in replacement for `Path` in most cases.
///
/// # Performance
/// `MaybePath` is a zero-runtime-cost abstraction over `Path` and `str`.  
/// Benchmarks show that `MaybePath` is faster than `Cow<Path>` for most operations:  
/// - Read: `798.20 ps` vs `1.5002 ns`
/// - Clone: `811.02 ps` vs `2.3745 ns`
///
/// However, it does store a `u8` to differentiate between `Path` and `str`,
/// which may increase memory usage for massive amounts of `MaybePath` instances.
///
/// # Safety
/// While it _is_ possible to access the underlying memory as-is with `as_path_unchecked` or `as_str_unchecked`,
/// it is not recommended to do so unless you are absolutely sure that the `MaybePath` is a `Path` or `str`.
///
/// However, in the current implementation of `Path`, all valid str's are valid paths - but this implementation detail may change in the future.
///
/// This implementation uses a union internally, since this method yields performance gains of up to 4x over using an enum.
///
/// # Examples
/// ```
/// use maybe_path::MaybePath;
///
/// let path = MaybePath::new_path("foo/bar/baz");
/// const PATH: MaybePath = MaybePath::new_str("foo/bar/baz");
/// ```
#[derive(Copy, Clone)]
pub struct MaybePath<'a> {
    kind: u8,
    inner: InnerMaybePath<'a>,
}

impl<'a> MaybePath<'a> {
    const KIND_PATH: u8 = 0x00;
    const KIND_STR: u8 = 0x01;

    /// Create a new `MaybePath` from a `str`.
    /// This can be done in const-time.
    ///
    /// # Examples
    /// ```
    /// use maybe_path::MaybePath;
    /// const PATH: MaybePath = MaybePath::new_str("foo/bar/baz");
    /// ```
    pub const fn new_str(str: &'a str) -> Self {
        Self {
            kind: Self::KIND_STR,
            inner: InnerMaybePath { str },
        }
    }

    /// Create a new `MaybePath` from a `Path`.  
    /// Due to as-ref, this cannot be done in const-time.
    ///
    /// # Examples
    /// ```
    /// use maybe_path::MaybePath;
    /// let path = MaybePath::new_path("foo/bar/baz");
    /// ```
    pub fn new_path<P: AsRef<OsStr> + ?Sized>(path: &'a P) -> Self {
        let path = Path::new(path);
        Self {
            kind: Self::KIND_PATH,
            inner: InnerMaybePath { path },
        }
    }

    /// Returns true if this `MaybePath` is a `Path`.  
    /// If false, `as_path` will have additional overhead
    #[inline]
    pub fn is_path(&self) -> bool {
        self.kind == Self::KIND_PATH
    }

    /// Returns a Path reference to the underlying data.
    /// If this `MaybePath` is a `str`, this will allocate a new `Path`.
    #[inline]
    pub fn as_path(&self) -> &'a Path {
        unsafe {
            if self.kind == Self::KIND_PATH {
                self.inner.path
            } else {
                Path::new(self.inner.str)
            }
        }
    }

    /// Returns a str reference to the underlying data.
    /// Could return none for a `Path` if the path is not valid utf-8.
    #[inline]
    pub fn as_str(&self) -> Option<&'a str> {
        unsafe {
            if self.kind == Self::KIND_PATH {
                self.inner.path.to_str()
            } else {
                Some(self.inner.str)
            }
        }
    }

    /// Returns a Path reference to the underlying data without checking if it is a `Path`.
    ///
    /// # Safety
    /// It is the caller's responsibility to ensure that this `MaybePath` is a `Path`.
    ///
    /// While the current implementation at time of writing virtually guarantees that all `str`'s are valid paths,  
    /// this is an implementation detail, and should not be relied upon.
    #[inline]
    pub unsafe fn as_path_unchecked(&self) -> &Path {
        self.inner.path
    }

    /// Returns a str reference to the underlying data without checking if it is a `str`.
    ///
    /// # Safety
    /// It is the caller's responsibility to ensure that this `MaybePath` is a `str`.
    ///
    /// While the current implementation at time of writing virtually guarantees that all `str`'s are valid paths,  
    /// The same cannot be said for paths being valid str's.
    ///
    /// It is possible for non-utf8 paths to exist, making this function unsafe.
    #[inline]
    pub unsafe fn as_str_unchecked(&self) -> &str {
        self.inner.str
    }

    /// Converts this `MaybePath` into a `PathBuf`.
    pub fn to_owned(&self) -> PathBuf {
        self.as_path().to_path_buf()
    }
}

impl Default for MaybePath<'_> {
    fn default() -> Self {
        Self::new_str("")
    }
}

impl Debug for MaybePath<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut dbg = f.debug_struct("MaybePath");

        if self.is_path() {
            dbg.field("path", &self.as_path());
        } else {
            dbg.field("str", &self.as_str());
        }

        dbg.finish()
    }
}

impl Ord for MaybePath<'_> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.as_path().cmp(other.as_path())
    }
}
impl PartialOrd for MaybePath<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for MaybePath<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.as_path() == other.as_path()
    }
}
impl Eq for MaybePath<'_> {}

impl Hash for MaybePath<'_> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        if self.is_path() {
            self.as_path().hash(state);
        } else {
            self.as_str().hash(state);
        }
    }
}

impl Display for MaybePath<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Display::fmt(&self.as_path().display(), f)
    }
}

impl Deref for MaybePath<'_> {
    type Target = Path;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.as_path()
    }
}

impl AsRef<Path> for MaybePath<'_> {
    #[inline]
    fn as_ref(&self) -> &Path {
        self
    }
}

impl serde::Serialize for MaybePath<'_> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.as_path().serialize(serializer)
    }
}