3

I need to simply (and dangerously - error handling omitted for brevity) get the current executable name. I made it work, but my function converts a &str to String only to call as_str() on it later for pattern matching.

fn binary_name() -> String {
    std::env::current_exe().unwrap().file_name().unwrap().to_str().unwrap().to_string()
}

As I understand it, std::env::current_exe() gives me ownership of the PathBuf which I could transfer by returning it. As it stands, I borrow it to convert it to &str. From there, the only way to return the string is to clone it before the PathBuf is dropped.

Is there any way to avoid this &OsStr -> &str -> String -> &str cycle?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Maciej Goszczycki
  • 1,118
  • 10
  • 25

1 Answers1

4

Is there a way to avoid cloning when converting a PathBuf to a String?

Absolutely. However, that's not what you are doing. You are taking a part of the PathBuf via file_name and converting that. You cannot take ownership of a part of a string.

If you weren't taking a subset, then converting an entire PathBuf can be done by converting to an OsString and then to a String. Here, I ignore the specific errors and just return success or failure:

use std::path::PathBuf;

fn exe_name() -> Option<String> {
    std::env::current_exe()
        .ok()
        .map(PathBuf::into_os_string)
        .and_then(|exe| exe.into_string().ok())
}

Is there any way to avoid this &OsStr -> &str -> String -> &str cycle?

No, because you are creating the String (or OsString or PathBuf, whichever holds ownership depending on the variant of code) inside your method. Check out Return local String as a slice (&str) for why you cannot return a reference to a stack-allocated item (including a string).

As stated in that Q&A, if you want to have references, the thing owning the data has to outlive the references:

use std::env;
use std::path::Path;
use std::ffi::OsStr;

fn binary_name(path: &Path) -> Option<&str> {
    path.file_name().and_then(OsStr::to_str)
}

fn main() {
    let exe = env::current_exe().ok();
    match exe.as_ref().and_then(|e| binary_name(e)) {
        Some("cat") => println!("Called as cat"),
        Some("dog") => println!("Called as dog"),
        Some(other) => println!("Why did you call me {}?", other),
        None => println!("Not able to figure out what I was called as"),
    }
}

Your original code can be written to not crash on errors easily enough

fn binary_name() -> Option<String> {
    let exe = std::env::current_exe();
    exe.ok()
        .as_ref()
        .and_then(|p| p.file_name())
        .and_then(|s| s.to_str())
        .map(String::from)
}
Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Neat. I thought there would be no way without moving ownership outside of the function. Just my C ways shining through. – Maciej Goszczycki Jan 23 '17 at 17:29
  • @mgoszcz2 I'm not sure I'm following you - in the first and third examples, new data is allocated, and in the second example, ownership *is* outside of the function. – Shepmaster Jan 23 '17 at 18:11
  • No it's just in C I would just return a pointer to `argv[0] + offset` – Maciej Goszczycki Jan 24 '17 at 03:01
  • @mgoszcz2 ah, yes, that's closest to the second example. `argv[0]`, in the name of `exe`, is passed into the function, and then the string is offset and terminated and returned, just with more error handling. – Shepmaster Jan 24 '17 at 03:09