Skip to content

Commit 1a44b45

Browse files
committed
Auto merge of rust-lang#113106 - marcospb19:improve-path-with-extension-function, r=thomcc
std: remove an allocation in `Path::with_extension` `Path::with_extension` used to reallocate (and copy) paths twice per call, now it does it once, by checking the size of the previous and new extensions it's possible to call `PathBuf::with_capacity` and pass the exact capacity required. This also reduces the memory consumption of the path returned from `Path::with_extension` by using exact capacity instead of using amortized exponential growth.
2 parents d26f0b7 + 6ffca76 commit 1a44b45

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

library/std/src/path.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -2608,9 +2608,27 @@ impl Path {
26082608
}
26092609

26102610
fn _with_extension(&self, extension: &OsStr) -> PathBuf {
2611-
let mut buf = self.to_path_buf();
2612-
buf.set_extension(extension);
2613-
buf
2611+
let self_len = self.as_os_str().len();
2612+
let self_bytes = self.as_os_str().as_os_str_bytes();
2613+
2614+
let (new_capacity, slice_to_copy) = match self.extension() {
2615+
None => {
2616+
// Enough capacity for the extension and the dot
2617+
let capacity = self_len + extension.len() + 1;
2618+
let whole_path = self_bytes.iter();
2619+
(capacity, whole_path)
2620+
}
2621+
Some(previous_extension) => {
2622+
let capacity = self_len + extension.len() - previous_extension.len();
2623+
let path_till_dot = self_bytes[..self_len - previous_extension.len()].iter();
2624+
(capacity, path_till_dot)
2625+
}
2626+
};
2627+
2628+
let mut new_path = PathBuf::with_capacity(new_capacity);
2629+
new_path.as_mut_vec().extend(slice_to_copy);
2630+
new_path.set_extension(extension);
2631+
new_path
26142632
}
26152633

26162634
/// Produces an iterator over the [`Component`]s of the path.

library/std/src/path/tests.rs

+45-5
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ pub fn test_prefix_ext() {
11831183
#[test]
11841184
pub fn test_push() {
11851185
macro_rules! tp (
1186-
($path:expr, $push:expr, $expected:expr) => ( {
1186+
($path:expr, $push:expr, $expected:expr) => ({
11871187
let mut actual = PathBuf::from($path);
11881188
actual.push($push);
11891189
assert!(actual.to_str() == Some($expected),
@@ -1281,7 +1281,7 @@ pub fn test_push() {
12811281
#[test]
12821282
pub fn test_pop() {
12831283
macro_rules! tp (
1284-
($path:expr, $expected:expr, $output:expr) => ( {
1284+
($path:expr, $expected:expr, $output:expr) => ({
12851285
let mut actual = PathBuf::from($path);
12861286
let output = actual.pop();
12871287
assert!(actual.to_str() == Some($expected) && output == $output,
@@ -1335,7 +1335,7 @@ pub fn test_pop() {
13351335
#[test]
13361336
pub fn test_set_file_name() {
13371337
macro_rules! tfn (
1338-
($path:expr, $file:expr, $expected:expr) => ( {
1338+
($path:expr, $file:expr, $expected:expr) => ({
13391339
let mut p = PathBuf::from($path);
13401340
p.set_file_name($file);
13411341
assert!(p.to_str() == Some($expected),
@@ -1369,7 +1369,7 @@ pub fn test_set_file_name() {
13691369
#[test]
13701370
pub fn test_set_extension() {
13711371
macro_rules! tfe (
1372-
($path:expr, $ext:expr, $expected:expr, $output:expr) => ( {
1372+
($path:expr, $ext:expr, $expected:expr, $output:expr) => ({
13731373
let mut p = PathBuf::from($path);
13741374
let output = p.set_extension($ext);
13751375
assert!(p.to_str() == Some($expected) && output == $output,
@@ -1394,6 +1394,46 @@ pub fn test_set_extension() {
13941394
tfe!("/", "foo", "/", false);
13951395
}
13961396

1397+
#[test]
1398+
pub fn test_with_extension() {
1399+
macro_rules! twe (
1400+
($input:expr, $extension:expr, $expected:expr) => ({
1401+
let input = Path::new($input);
1402+
let output = input.with_extension($extension);
1403+
1404+
assert!(
1405+
output.to_str() == Some($expected),
1406+
"calling Path::new({:?}).with_extension({:?}): Expected {:?}, got {:?}",
1407+
$input, $extension, $expected, output,
1408+
);
1409+
});
1410+
);
1411+
1412+
twe!("foo", "txt", "foo.txt");
1413+
twe!("foo.bar", "txt", "foo.txt");
1414+
twe!("foo.bar.baz", "txt", "foo.bar.txt");
1415+
twe!(".test", "txt", ".test.txt");
1416+
twe!("foo.txt", "", "foo");
1417+
twe!("foo", "", "foo");
1418+
twe!("", "foo", "");
1419+
twe!(".", "foo", ".");
1420+
twe!("foo/", "bar", "foo.bar");
1421+
twe!("foo/.", "bar", "foo.bar");
1422+
twe!("..", "foo", "..");
1423+
twe!("foo/..", "bar", "foo/..");
1424+
twe!("/", "foo", "/");
1425+
1426+
// New extension is smaller than file name
1427+
twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb");
1428+
// New extension is greater than file name
1429+
twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa");
1430+
1431+
// New extension is smaller than previous extension
1432+
twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.bbb_bbb");
1433+
// New extension is greater than previous extension
1434+
twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.aaa_aaa_aaa");
1435+
}
1436+
13971437
#[test]
13981438
fn test_eq_receivers() {
13991439
use crate::borrow::Cow;
@@ -1669,7 +1709,7 @@ fn into_rc() {
16691709
#[test]
16701710
fn test_ord() {
16711711
macro_rules! ord(
1672-
($ord:ident, $left:expr, $right:expr) => ( {
1712+
($ord:ident, $left:expr, $right:expr) => ({
16731713
use core::cmp::Ordering;
16741714

16751715
let left = Path::new($left);

0 commit comments

Comments
 (0)