dirbuf 0.1.2

reusable directory buffers
Documentation
//! basic methods that all other methods are built out of.

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

/// Reusable directory buffer.
///
/// While the state of the internal buffer may change,
/// semantically, it *always* has the same value as when it was constructed.
/// This is to prevent you from accidentally making a permanent modification
/// to its path instead of a temporary one.
#[derive(Default, Debug)]
pub struct DirBuf {
    // SAFETY: neither field should be accessed directly, instead use `reset`.
    buf: PathBuf,
    // SAFETY: must not be modified after initial construction
    initial_len: usize,
}

impl DirBuf {
    /// Construct a new [`DirBuf`] representing the given path.
    ///
    /// Note that this path does *not* need to be a trivial path.
    #[must_use]
    pub fn new(path: impl Into<PathBuf>) -> Self {
        let buf = path.into();
        DirBuf {
            initial_len: buf.as_os_str().len(),
            buf,
        }
    }

    /// has the same safety `invariants` as `DirBufEntry::reset`
    pub(crate) unsafe fn reset(&mut self) -> &mut PathBuf {
        let ilen = self.initial_len;
        let mut entry = self.as_entry();
        entry.initial_len = ilen;
        // SAFETY: same invariants, which must be guranteed by caller
        unsafe { entry.reset() };
        // gotta return this here to work around reborrow lifetime limitations.
        &mut self.buf
    }

    // bypasses the semantic immutability, so must be called in conjunction with `reset`.
    pub(crate) fn as_entry(&mut self) -> DirBufEntry<'_> {
        DirBufEntry{
            initial_len: self.buf.as_os_str().len(),
            buf: &mut self.buf,
        }
    }

    pub(crate) unsafe fn push(&mut self, path: &Path) {
        self.as_entry().push(path);
    }
}

/// Represents an entry within a [`DirBuf`] by reusing its internal buffer.
///
/// A `DirBufEntry` holds a mutable borrow of its parent,
/// preventing the buffer from being unexpectedly modified while in use.
#[derive(Debug)]
pub struct DirBufEntry<'buf> {
    // SAFETY: neither field should be accessed directly, instead use `reset`.
    buf: &'buf mut PathBuf,
    /// initial length of *this entry*, not of the underlying
    // SAFETY: must not be modified after initial construction
    initial_len: usize,
}

impl DirBufEntry<'_> {
    /// The core logic that keeps `DirBuf` and `DirBufEntry` semantically immutable.
    ///
    /// # Safety
    /// The returned `PathBuf` must not be modified except
    /// for by appending to it,
    /// otherwise the `DirBuf` will enter an invalid state,
    /// and any further operations
    /// on it are unsound.
    pub(crate) unsafe fn reset(&mut self) -> &mut PathBuf {
        let os_path = self.buf.as_mut_os_string();
        // SAFETY: `reset` is the only function that exposes the internal PathBuf,
        // and it requires that said buffer is not modified execpt by appending.
        // This ensures, by construction, that `initial_len` falls on a valid OsStr boundary, thus ensuring this truncation is sound.
        #[cfg_attr(feature = "nightly-safe-impl", expect(unused_unsafe))]
        unsafe { truncate_os_string(os_path, self.initial_len) };
        self.buf
    }

    // bypasses the semantic immutability, so must be called in conjunction with `reset`.
    pub(crate) fn as_entry(&mut self) -> DirBufEntry<'_> {
        DirBufEntry{
            initial_len: self.buf.as_os_str().len(),
            buf: self.buf,
        }
    }

    // SAFETY: the path must be trivial
    pub(crate) unsafe fn push(&mut self, path: &Path) {
        // SAFETY: only modifies by appending, satisfying the invariant of `reset`
        unsafe {self.reset()}.push(path);
    }
}

impl AsRef<Path> for DirBuf {
    fn as_ref(&self) -> &Path {
        // SAFETY: initial_len is guranteed by construction to lie on a valid OsStr boundry
        unsafe { path_prefix_bytes(&self.buf, self.initial_len) }
    }
}

impl AsRef<Path> for DirBufEntry<'_> {
    fn as_ref(&self) -> &Path {
        // SAFETY: initial_len is guranteed by construction to lie on a valid OsStr boundry
        unsafe { path_prefix_bytes(self.buf, self.initial_len) }
    }
}

/// # Safety
/// `len` must lie on a valid `OsStr` boundry.
unsafe fn path_prefix_bytes(path: &Path, len: usize) -> &Path {
    let slice = &path.as_os_str().as_encoded_bytes()[..len];
    // SAFETY: invariant guranteed by caller
    Path::new(unsafe {OsStr::from_encoded_bytes_unchecked(slice)})
}

/// # Safety
/// `l` must lie on a valid `OsStr` boundary.
#[cfg(not(feature = "nightly-safe-impl"))]
unsafe fn truncate_os_string(s: &mut OsString, l: usize) {
    // gotta do an annoying dance because OsString lacks a stable truncate method
    let mut os_string_tmp = OsString::new();
    std::mem::swap(s, &mut os_string_tmp);
    let mut vec = os_string_tmp.into_encoded_bytes();
    vec.truncate(l);
    // SAFETY: we are resetting to an earlier state,
    // which must be valid since we got it from safe code.
    os_string_tmp = unsafe { OsString::from_encoded_bytes_unchecked(vec) };
    std::mem::swap(s, &mut os_string_tmp);
}

/// # Panics
/// `l` must lie on a valid `OsStr` boundary.
#[cfg(feature = "nightly-safe-impl")]
fn truncate_os_string(s: &mut OsString, l: usize) {
    s.truncate(l);
}