Skip to content

Commit eafaf5d

Browse files
committed
Add PathBuf wrapper to reduce allocations when working with many files with the same parent
1 parent 0d8b9de commit eafaf5d

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1515

1616
#![forbid(unsafe_code)]
17+
#![feature(os_string_truncate)]
1718

1819
pub mod file_tree;
1920
pub mod instance;
21+
pub mod util;

core/src/util.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright © 2026 Joaquim Monteiro
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
//! Miscellaneous functions.
17+
18+
use std::path::{Path, PathBuf};
19+
20+
/// A `PathBuf` that can be truncated to a base path without re-allocating.
21+
pub struct ResettablePathBuf {
22+
buffer: PathBuf,
23+
base_length: usize,
24+
}
25+
26+
impl ResettablePathBuf {
27+
/// Creates a new `ResettablePathBuf` with the provided path as the base.
28+
#[must_use]
29+
pub fn new(base: PathBuf) -> Self {
30+
Self { base_length: base.as_os_str().len(), buffer: base }
31+
}
32+
33+
/// Truncates the path to its base.
34+
pub fn reset_to_base(&mut self) {
35+
self.buffer.as_mut_os_string().truncate(self.base_length);
36+
}
37+
38+
/// Sets the current path as the new base.
39+
pub fn set_base_to_current(&mut self) {
40+
self.base_length = self.buffer.as_os_str().len();
41+
}
42+
43+
/// Extends the path with the specified relative path.
44+
#[inline]
45+
pub fn push<P: AsRef<Path>>(&mut self, path: P) -> &Path {
46+
self.push_inner(path.as_ref())
47+
}
48+
49+
fn push_inner(&mut self, path: &Path) -> &Path {
50+
assert!(path.is_relative());
51+
self.buffer.push(path);
52+
&self.buffer
53+
}
54+
}
55+
56+
impl AsRef<Path> for ResettablePathBuf {
57+
fn as_ref(&self) -> &Path {
58+
&self.buffer
59+
}
60+
}
61+
62+
#[cfg(test)]
63+
mod tests {
64+
use super::*;
65+
use std::path::Path;
66+
67+
#[test]
68+
fn resettable_path_buf() {
69+
let base = Path::new("/a/b/c");
70+
let mut p = ResettablePathBuf::new(base.to_path_buf());
71+
assert_eq!(p.as_ref(), base);
72+
73+
assert_eq!(p.push("d/e"), Path::new("/a/b/c/d/e"));
74+
assert_eq!(p.push("f"), Path::new("/a/b/c/d/e/f"));
75+
76+
p.reset_to_base();
77+
assert_eq!(p.as_ref(), base);
78+
assert_eq!(p.push("0"), Path::new("/a/b/c/0"));
79+
80+
p.set_base_to_current();
81+
assert_eq!(p.push("123"), Path::new("/a/b/c/0/123"));
82+
p.reset_to_base();
83+
assert_eq!(p.as_ref(), Path::new("/a/b/c/0"));
84+
}
85+
86+
#[test]
87+
#[should_panic(expected = "assertion failed: path.is_relative()")]
88+
fn resettable_path_buf_push_absolute() {
89+
let mut p = ResettablePathBuf::new(PathBuf::from("/a/b/c"));
90+
p.push("/etc/passwd");
91+
}
92+
}

0 commit comments

Comments
 (0)