Generic function for both OsStr and Path

Hello,

Could you please tell me, is it possible to write a generic function that take OsStr(ing) and Path(Buf) and return OsStr/Path?

I'm trying to write strip_prefix and split_once that work some as the &str functions. So far I was able to write strip_prefix that works:

pub trait OsStrUtil {
    fn osstr_strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr>;
}
impl<Y> OsStrUtil for Y
where
    Y: ?Sized + AsRef<OsStr>,
{
    fn osstr_strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr> {
        self.as_ref()
            .as_bytes()
            .strip_prefix(prefix.as_bytes())
            .map(|inp| OsStr::from_bytes(inp))
    }
}

pub trait PathUtil {
    fn path_strip_prefix(&self, prefix: &Path) -> Option<&Path>;
}
impl<Y> PathUtil for Y
where
    Y: ?Sized + AsRef<Path>,
{
    fn path_strip_prefix(&self, prefix: &Path) -> Option<&Path> {
        self.as_ref()
            .as_os_str()
            .as_bytes()
            .strip_prefix(prefix.as_os_str().as_bytes())
            .map(|inp| Path::new(OsStr::from_bytes(inp)))
    }
}

fn main() {
    let x = OsStr::new("abcdef");
    let x = x.osstr_strip_prefix(OsStr::new("abc"));
    assert_eq!(x, Some(OsStr::new("def")));

    let x = Path::new("abcdef");
    let x = x.path_strip_prefix(Path::new("abc"));
    assert_eq!(x, Some(Path::new("def")));
}

but I was unable to create a single generic function without compiler complaining about something.

Maybe that's not possible and that's OK too!

Thank you!

1 Like

How about this

pub fn strip_prefix<'a, S>(s: &'a S, prefix: &S) -> Option<&'a S>
where
    S: AsRef<OsStr> + ?Sized,
    OsStr: AsRef<S>,
{
    s.as_ref()
        .as_bytes()
        .strip_prefix(prefix.as_ref().as_bytes())
        .map(|inp| OsStr::from_bytes(inp).as_ref())
}
2 Likes

That's awesome @drewtato ! Thank you! I had no idea I can write OsStr: AsRef<S>.

Do you know what's wrong with the trait implementation? For some reason I have problems to turn it into a trait...

pub trait OsStrUtil<S> {
    fn strip_prefix<'a>(&'a self, prefix: &S) -> Option<&'a S>
    where
        Self: AsRef<OsStr>,
        S: AsRef<OsStr>,
        OsStr: AsRef<S>;
}
impl<S> OsStrUtil<S> for S {
    fn strip_prefix<'a>(&'a self, prefix: &S) -> Option<&'a S>
    where
        Self: AsRef<OsStr>,
        S: AsRef<OsStr>,
        OsStr: AsRef<S>,
    {
        self.as_ref()
            .as_bytes()
            .strip_prefix(prefix.as_ref().as_bytes())
            .map(|inp| OsStr::from_bytes(inp).as_ref())
    }
}

fn main() {
    let x = OsStr::new("abcdef");
    let x = x.strip_prefix(OsStr::new("abc"));
    assert_eq!(x, Some(OsStr::new("def")));

    let x = Path::new("abcdef");
    let x = x.strip_prefix(Path::new("abc"));
    assert_eq!(x, Some(Path::new("def")));
}

I'd probably do it like this:

pub trait OsStrUtil: AsRef<OsStr>
where
    OsStr: AsRef<Self>,
{
    fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self>;
}

impl<S> OsStrUtil for S
where
    S: AsRef<OsStr> + ?Sized,
    OsStr: AsRef<Self>,
{
    fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self> {
        self.as_ref()
            .as_bytes()
            .strip_prefix(prefix.as_ref().as_bytes())
            .map(|inp| OsStr::from_bytes(inp).as_ref())
    }
}

Or if you want to be able to pass a different prefix type than Self, you can put a generic on the function.

pub trait OsStrUtil: AsRef<OsStr>
where
    OsStr: AsRef<Self>,
{
    fn strip_prefix<'a, P: AsRef<OsStr> + ?Sized>(&'a self, prefix: &P) -> Option<&'a Self>;
}

impl<S> OsStrUtil for S
where
    S: AsRef<OsStr> + ?Sized,
    OsStr: AsRef<Self>,
{
    fn strip_prefix<'a, P: AsRef<OsStr> + ?Sized>(&'a self, prefix: &P) -> Option<&'a Self> {
        self.as_ref()
            .as_bytes()
            .strip_prefix(prefix.as_ref().as_bytes())
            .map(|inp| OsStr::from_bytes(inp).as_ref())
    }
}
1 Like

Thank you very much @drewtato . Unfortunately there is a problem converting into Path

fn main() {
    let x = OsStr::new("abcdef");
    let x = x.strip_prefix(OsStr::new("abc"));
    assert_eq!(x, Some(OsStr::new("def")));

    let x = Path::new("abcdef");
    let x = x.strip_prefix(Path::new("abc"));
    assert_eq!(x, Some(Path::new("def")));
    //            ^^^^^^^^^^^^^^^^^^^^^^ expected `Result<&Path, StripPrefixError>`, found `Option<&Path>`
}

That probably has something to do with more restrictions on Path...

That's because Path has an inherent method with the same name but returns a different value. Changing the method name in your extension trait is the simplest solution.

1 Like

Yes, thank you very much folks!

pub trait OsStrUtil: AsRef<OsStr>
where
    OsStr: AsRef<Self>,
{
    fn strip_prefix_os<'a, P: AsRef<OsStr> + ?Sized>(&'a self, prefix: &P) -> Option<&'a Self>;
}

impl<S> OsStrUtil for S
where
    S: AsRef<OsStr> + ?Sized,
    OsStr: AsRef<Self>,
{
    fn strip_prefix_os<'a, P: AsRef<OsStr> + ?Sized>(&'a self, prefix: &P) -> Option<&'a Self> {
        self.as_ref()
            .as_bytes()
            .strip_prefix(prefix.as_ref().as_bytes())
            .map(|inp| OsStr::from_bytes(inp).as_ref())
    }
}

fn main() {
    let x = OsStr::new("abcdef");
    let x = x.strip_prefix_os(OsStr::new("abc"));
    assert_eq!(x, Some(OsStr::new("def")));

    let x = Path::new("abcdef");
    let x = x.strip_prefix_os(Path::new("abc"));
    assert_eq!(x, Some(Path::new("def")));
}

@parasyte , @drewtato do you think you could please help me with the second function? I thought split_once will be easy after strip_prefix but apparently not :wink:

use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;

pub trait OsStrUtil: AsRef<OsStr>
where
    OsStr: AsRef<Self>,
{
    fn split_once_os<'a, P: AsRef<OsStr> + ?Sized>(&'a self, separator: &P) -> Option<(&'a Self, &'a Self)>;
}

impl<S> OsStrUtil for S
where
    S: AsRef<OsStr> + ?Sized,
    OsStr: AsRef<Self>,
{
    fn split_once_os<'a, P: AsRef<OsStr> + ?Sized>(&'a self, separator: &P) -> Option<(&'a Self, &'a Self)> {
        self.as_ref()
            .as_bytes()
            //////////////
            ////.split_once(separator.as_ref().as_bytes())
            ///  use of unstable library feature 'slice_split_once': newly added
            //////////////
            .map(|(left, right)| (OsStr::from_bytes(left).as_ref(), OsStr::from_bytes(right).as_ref()))
    }
}

fn main() {
    let x = OsStr::new("abcdef");
    let x = x.split_once_os(OsStr::new("cd"));
    assert_eq!(x, Some((OsStr::new("ab"), OsStr::new("ef"))));

    let x = Path::new("abcdef");
    let x = x.split_once_os(Path::new("cd"));
    assert_eq!(x, Some((Path::new("ab"), Path::new("ef"))));
}

split_once doesn't even do what you want it to, it takes a single element while you want to split on a string. You just have to do it yourself.

fn split_once_os<'a, P: AsRef<OsStr> + ?Sized>(
    &'a self,
    separator: &P,
) -> Option<(&'a Self, &'a Self)> {
    let slice = self.as_ref().as_bytes();
    let separator = separator.as_ref().as_bytes();

    let index = std::iter::successors(Some(slice), |slice| slice.get(1..))
        .map_while(|slice| slice.get(..separator.len()))
        .position(|slice| slice == separator)?;

    let (left, rest) = slice.split_at(index);
    let right = &rest[separator.len()..];

    Some((
        OsStr::from_bytes(left).as_ref(),
        OsStr::from_bytes(right).as_ref(),
    ))
}
1 Like

That's fantastic, thank you.