One-liner to get Path from args()

I'm quite new to Rust and I'm still in the "add and remove ampersands until it works" phase.

I'm trying to get a std::path::Path from command line argument 0 (so that I can call .parent() on it and find out where the current executable is).

The naive solution doesn't work:

use std::env;
use std::path::{Path};

fn main() {
    let d = Path::new(&env::args().next().unwrap()).parent().unwrap();
    println!("Dir: {}", d.to_string_lossy());
}
error[E0716]: temporary value dropped while borrowed
 --> src\main.rs:5:24
  |
5 |     let d = Path::new(&env::args().next().unwrap()).parent().unwrap();
  |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^                   - temporary value is freed at the end of this statement
  |                        |
  |                        creates a temporary which is freed while still in use
6 |     println!("Dir: {}", d.to_string_lossy());
  |                         - borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

Let's ignore the hint for now because I'm looking for a one-liner.

So I read up on Path and I found that apparently because String is owned, I need PathBuf if I want to consume the value. So I tried:

let d = PathBuf::from(env::args().next().unwrap()).parent().unwrap();

But to no avail:

error[E0716]: temporary value dropped while borrowed
 --> src\main.rs:5:13
  |
5 |     let d = PathBuf::from(env::args().next().unwrap()).parent().unwrap();
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                  - temporary value is freed at the end of this statement
  |             |
  |             creates a temporary which is freed while still in use
6 |     println!("Dir: {}", d.to_string_lossy());
  |                         - borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

This probably has something to do with the fact that PathBuf::parent() returns a reference but I don't understand the intention here.

Where do I have to add an ampersand/asterisk? :smiley:

Since parent is just a slice of the path, you have to keep the original around.

use std::env;
use std::path::{Path};

fn main() {
    let p = PathBuf::from(env::args().next().unwrap());
    let d = p.parent().unwrap();
    println!("Dir: {}", d.to_string_lossy());
}

No, you can't solve it with more/less ampersands. But since it's Rust not Python, you can write whatever code in one line.

use std::env; use std::path::Path; fn main() { let d = env::args().next().unwrap(); let d = Path::new(&d).parent().unwrap(); println!("Dir: {}", d.to_string_lossy()); }

Wouldn't that mean I can never write a function that returns some_path.parent()?

That's not what I meant. :slight_smile: I'm not concerned with the actual number of lines, I'm looking for a single expression in order to understand the language better. I am currently assuming that let a = foo(); bar(a); can always be transformed into bar(foo()); and vice versa. If that assumption is wrong, then that's an aspect of Rust that I haven't understood yet.

Yes. Unlike many other languages, variables affect the value's lifetime so you can't just replace it with inline expression.

Learning Rust is all about to reason who owns what. If you want to use a variable named d whose type is &Path, you, and the compiler should know who owns that Path value, since it's a reference. From the code it's obvious it's the String returned by env::args().next().unwrap(). But since nobody takes its ownership it would be dropped at the nearest statement boundary, in this case it's the end of the let d = .... So the d variable would contains dangling reference, which is not allowed in Rust the memory safe language, so the compiler rejects to compile.

You may ask, if it's obvious, why does the compiler don't guess its correct lifetime instead of just throwing error? In fact, the compiler never guess about lifetime*. As a system programming language, it matters predictability and performance more than the just-working code. All resources including allocated memories have statically defined lifetime. The borrowed references never do more than the dumb pointers at runtime, and never modifies the value's lifetime. Instead of running some runtime efforts to ensure memory safety, the compiler rejects to compile that can violates memory safety.

2 Likes

Not unless some_path was an argument to the function. What you can do is convert the parent to a PathBuf and return that. You can also use PathBuf::pop to modify an existing PathBuf to be its parent.

After unwrap you can call to_owned().

1 Like

Ok, so I think I got it...

The problem is that

let d = PathBuf::from(env::args().next().unwrap()).parent().unwrap();

is essentially the same as

{
  let p = env::args().next().unwrap();
}
let d = PathBuf::from(p).parent().unwrap();

which obviously doesn't work either, right?

So while named variables live until the closing brace of their scope, unnamed expressions only live until the end of the statement?

1 Like

This seems to be the relevant documentation:

https://doc.rust-lang.org/reference/expressions.html#temporary-lifetimes

I should really read "the reference", not just "the book".

What about just using env::current_exe()?

2 Likes

The problem is not the ownership of the original path, but the ownership of the parent. So this solves your problem by making d a PathBuf.

let d = Path::new(&env::args().next().unwrap()).parent().unwrap().to_owned();

To break it down further:

let owned_parent: PathBuf = {
    let arg: String = env::args().next().unwrap();  // This is the owned temporary value that's causing you problems.  This needs to live longer or be cloned.  Here, we clone it.
    let path: &Path = Path::new(&arg);
    let parent: &Path = path.parent().unwrap();
    parent.to_owned()
}

If you want to avoid cloning it, you'll want to do something like:

let arg: String = env::args().next().unwrap();
let mut path: PathBuf = PathBuf::from(arg); // Takes ownership of arg
if !path.pop() { // This is the self-mutating version of .parent()
    panic!("or handle this gracefully, but this mimics the behavior of your example")
}
println!("Dir: {}", path.to_string_lossy());

You can't quite make a single expression out of this (but that's never actually a useful goal anyway), because pop doesn't return the value of the PathBuf.

println!("Dir: {}", PathBuf::from(env::args().next().unwrap()).pop()

That would have better cross platform behaviour. The first argument isn't guaranteed to be the application path on all platforms.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.