Append an additional extension?

Let’s say I have a Path: foo.bar, and I’d like to append an extension baz to get: foo.bar.baz. Right now there seem to be no easy way to do it.

Remember that Path is analogous to str and that PathBuf is likewise analogous to String, and you can build on it.

How? I can’t format! or append_str to a PathBuf. Everything is working on component level, plus couple of methods for extension and file stem. Am I missing something?

Well, if you want it in your format, then you can push and then replace on a .to_string call to replace / with .. I’m on mobile so I can’t add a playground example

No. I want a PathBuf. I need to rename a directory to a ${original_dir}.tmp, but the original_dir might already have dots in it.

Why not try the following:

  1. Get the last component in path_buf.components() and cast it’s as_os_str to an owned string
  2. Add what you want to it
  3. Call pop on path_buf
  4. Push your new string onto your path_buf

This is missing a check to make sure that it is a directory, so be sure that it either is, or run some checks on user input
Edit: it’s actually abit more complicated than that because it doesn’t go straight to String and instead its os variant, so that’s something to keep in mind

Yeah, so I have something like this, but it’s terribly clunky. I’m surprised that stdlib provided methods to replace the extension, but not append, which is usually what I would actually like (eg. for tmp files etc)

Oh, what about a cleaner way where you call into_os_string on the pathbuf and just append and form a new pathbuf?
I know that this isn’t very non-clunky but I can’t figure out another way without some better context

How do you append to OsString without converting to Vec<u8>?

You can push as better exemplified by the examples on the docs

1 Like

I’ve totally missed it. Thank you!

Hi all,

I'm reviving this thread. There's a set_extension which replaces an extension but no add_extension .

For example:

    use std::path::PathBuf;

    let mut path = PathBuf::new();

    path.push("/var/log");
    path.push("kern.log");
    path.set_extension("gz");

    println!("{:?}", path);

prints out /var/log/kern.gz and not /var/log/kern.log.gz . Any clue ?

Thanks for your help.

I don't believe there's a function for it in std. How about this?

fn add_extension(path: &mut std::path::PathBuf, extension: impl AsRef<std::path::Path>) {
    match path.extension() {
        Some(ext) => {
            let mut ext = ext.to_os_string();
            ext.push(".");
            ext.push(extension.as_ref());
            path.set_extension(ext)
        }
        None => path.set_extension(extension.as_ref()),
    };
}

fn main() {
    let mut path = std::path::PathBuf::from("/var/log/kern.log");
    add_extension(&mut path, "gz");
    assert_eq!(path.as_os_str(), "/var/log/kern.log.gz");
}

If you only need to do it once, or you know for sure the path has an extension, you can do it without a function or in fewer lines.

2 Likes

@Heliozoa Thanks for your contribution.

I'm a little bit reluctant to move to strings, but if this is the only way I'll make it.

BTW, is this add_extension an opportunity for a PR ?

As long as it's encapsulated in a function there shouldn't be any problem. A PathBuf is just a newtype wrapper around OsString which gives us methods specific to file paths, and the standard library deliberately exposes From<OsString> and Into<OsString> as an escape hatch for exactly these situations where the provided functionality is missing something.

Yep. I'd create an issue first to gauge interest, probably including a potential implementation, and iron out the edge cases (e.g. what happens if the path is empty, or if it's just pointing to the C: drive?).

Reviving the thread again.
I went ahead to the issues tracker, but as of time of writing it recommends to start a discussion first, so I did: PathBuf has set_extension but no add_extension. Cannot cleanly turn .tar to .tar.gz - libs - Rust Internals

1 Like