Pre-1.68 equivalent to DerefMut for PathBuf

impl DerefMut for PathBuf was only added to the standard library in Rust 1.68 (the latest version until Thursday). I'm writing a library with a method that makes use of this feature, and I'd like to rewrite it so that it works on older Rusts as well. According to the implementation PR, the pre-1.68 way to get a &mut Path from a PathBuf was via PathBuf::into_boxed_path, but that doesn't work for my purposes, as it consumes the PathBuf, yet the method has a signature of get_mut(&mut self) -> &mut Path, where Self is a struct with a PathBuf field.

Is there a way to extract a &mut Path from a mutable reference to a struct with a PathBuf field in pre-1.68, and how does it work?

unfortunately, the std library doesn't expose implementation details, I'm afraid you can't implement itn in the way you intended, but depending on what you are doing with the mut reference, it might be an alternative if your use case can accept an CPS/callbacks/scoped style API. there's an example in the same PR thread you linked, in this comment

Unfortunately, this is non-trivial. The safest way by far is to cheat a bit and swap out the PathBuf's buffer until you're done with it (Rust Playground):

use std::{
    mem, ops,
    path::{Path, PathBuf},
};

pub fn get_mut(path_buf: &mut PathBuf) -> impl ops::Deref<Target = Path> + ops::DerefMut + '_ {
    struct PathMut<'a>(&'a mut PathBuf, Box<Path>);
    impl Drop for PathMut<'_> {
        fn drop(&mut self) {
            let path = mem::replace(&mut self.1, PathBuf::new().into_boxed_path());
            *self.0 = path.into_path_buf();
        }
    }
    impl ops::Deref for PathMut<'_> {
        type Target = Path;
        fn deref(&self) -> &Self::Target {
            &self.1
        }
    }
    impl ops::DerefMut for PathMut<'_> {
        fn deref_mut(&mut self) -> &mut Self::Target {
            &mut self.1
        }
    }
    let path = mem::take(path_buf).into_boxed_path();
    PathMut(path_buf, path)
}

A dangerous way (that many will frown upon) is to take the documentation's word for it that PathBuf is a thin wrapper over OsString (Rust Playground):

use std::{
    ffi::{OsStr, OsString},
    mem,
    path::{Path, PathBuf},
};

pub fn get_mut(path_buf: &mut PathBuf) -> &mut Path {
    // sanity check
    const _: () = assert!(mem::size_of::<PathBuf>() == mem::size_of::<OsString>());
    // SAFETY: `PathBuf` contains an `OsString`, and it is not large enough to
    // contain any fields before it.
    let os_string = unsafe { &mut *(path_buf as *mut PathBuf as *mut OsString) };
    let os_str = &mut **os_string;
    // SAFETY: The existence of `AsRef<OsStr> for str` and `AsRef<Path> for str`
    // implies that `OsStr` and `Path` can store arbitrary code units of size 1 and
    // alignment 1. Unless the language is changed to add more kinds of DSTs, this
    // means that `OsStr` and `Path` are both slice DSTs with size-1 alignment-1
    // elements. Therefore, their DST metadata are compatible, and they can be
    // soundly converted via a pointer cast.
    let path = unsafe { &mut *(os_str as *mut OsStr as *mut Path) };
    path
}

A somewhat less dangerous (but still very sketchy) way is to rely on PathBuf and OsString not to assert unique ownership over their internal buffers (Rust Playground):

use std::{
    ffi::OsStr,
    mem,
    path::{Path, PathBuf},
};

pub fn get_mut(path_buf: &mut PathBuf) -> &mut Path {
    let mut os_string = mem::take(path_buf).into_os_string();
    let os_str = &mut *os_string;
    // SAFETY: As before. Also, neither `PathBuf` nor `OsString` asserts unique
    // ownership over its internal buffer, so the reference is not invalidated when
    // we move the `OsString` back.
    let path = unsafe { &mut *(os_str as *mut OsStr as *mut Path) };
    *path_buf = os_string.into();
    path
}

The question is, what do you want to do with a &mut Path in the first place? The type has no special APIs: everything that can be done with a &mut Path (without making unsafe layout assumptions) can also be done with a &Path. That's why there aren't any &mut OsStr -> &mut Path APIs or similar, since the latter type is basically useless.

3 Likes

That's ... a good point that I somehow missed.

To explain what I'm actually doing: I'm writing a library with an enum with two variants; one variant has a PathBuf field, the other does not. I thought it'd be useful if there was a method that returned an Option containing a reference to the PathBuf, and for reasons that I'm not going to try to articulate, I thought a return type of Option<&Path> (yes, &Path, not &PathBuf) would be more "idiomatic" somehow, and that thought was followed up with "What about a &mut version of this method?" Now that I've realized a &mut Path is pointless, I should probably change the methods in question to return Option<&PathBuf> and Option<&mut PathBuf> — or would it be simpler to get rid of them altogether? I do not know what is conventional here.