11
\$\begingroup\$
fn basename<'a>(path: &'a str, sep: char) -> Cow<'a, str> {
    let pieces = path.split(sep);
    match pieces.last() {
        Some(p) => p.into(),
        None => path.into(),
    }
}

Usage:

println!("'{}'", basename("foo", '/'));    // outputs 'foo'
println!("'{}'", basename("bob/", '/'));   // outputs ''
println!("'{}'", basename("/usr/local/bin/rustc", '/')); // outputs 'rustc'

I think the split() into a match on last() is kind of elegant. I know there is some work needed to handle both str and String, I am not sold on the use of Cow and needing to define a lifetime for the string.

I am not sold on Cow because later on I need to extract from it.

let prog = basename(&args[0], '/').into_owned();

It feels like I am working too hard.

\$\endgroup\$
2
  • \$\begingroup\$ I'm not very familiar with Rust, but in other languages I would use lastIndexOf to find the last occurrence of a substring within a string. \$\endgroup\$ Commented Jul 29, 2015 at 23:24
  • \$\begingroup\$ Yup, that is what last() is doing @SimonAndréForsberg. In Rust the split() makes an iterator. Converting it to an array or the like would be a bit of overkill. \$\endgroup\$
    – Sean Perry
    Commented Jul 29, 2015 at 23:26

2 Answers 2

12
\$\begingroup\$

Firstly, you should use rsplit and next rather than split and last, as it starts at the more appropriate end:

fn basename<'a>(path: &'a str, sep: char) -> Cow<'a, str> {
    let pieces = path.rsplit(sep);
    match pieces.next() {
        Some(p) => p.into(),
        None => path.into(),
    }
}

Secondly, you shouldn’t be using strings for this; you should be using paths, because that’s semantically what you’re dealing with.

The easiest way to get a path tends to be to take a &Path or a generic parameter implementing AsRef<Path> and calling .as_ref() on it; str, String, Path, PathBuf and more implement it.

You can get the base name from a &Path with file_name; this admittedly produces a Option<&OsStr>, so if you want to display the path you’d need to convert it back towards a string with e.g. .and_then(|s| s.to_str()).

Anyway, the point of this latter part is just that for something that is semantically a path, you should be handling it specially, as a rule; a path need not be Unicode. Think on it more.

\$\endgroup\$
1
  • \$\begingroup\$ A few problems with this answer. I'm new to Rust, and one of the 1st tasks is processing command line args, which includes the program path in args[0]. First, it was unclear from code example above what "use" dependencies there are (use std::borrow::Cow;). Why even depend on Cow, and what is it? 2nd, the syntax seems peculiar, and the 'a throws off syntax highlighting, further making it needlessly confusing. Third, it requires a path separator. I'm on windows, and depending if I call from a command prompt or MSYS2, the separator can be / or \ . How to determine that dynamically? \$\endgroup\$
    – user137777
    Commented May 1, 2017 at 23:46
-1
\$\begingroup\$
pub fn basename<'a>(path: &'a str) -> Cow<'a, str> {
    let mut pieces = path.rsplitn(2, |c| c == '/' || c == '\\');
    match pieces.next() {
        Some(p) => p.into(),
        None => path.into(),
    }
}

This works for Unix or Windows path separators and does the least amount of work since it breaks the string into two pieces, the basename and the rest.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ Commented Dec 13, 2018 at 9:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.