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.
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. 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.
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.
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.