why do I need to use `let` binding [duplicate] - rust

This question already has answers here:
Why does the compiler tell me to consider using a `let` binding" when I already am?
(2 answers)
Closed last year.
While trying to print out my home directory I came across the following error:
fn main() {
let home = home::home_dir().unwrap().to_str().unwrap();
println!("home dir: {}", home);
}
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:3:16
|
3 | let home = home::home_dir().unwrap().to_str().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
4 | println!("home dir: {}", home);
| ---- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
But if I split the part underlined in the error message and put it in a separate variable it works:
fn main() {
let home_buf = home::home_dir().unwrap();
let home_str = home_buf.to_str().unwrap();
println!("home dir: {}", home_str);
}
My question is what's the difference between the two examples?
(I'm using the home crate in my code)

home::home_dir().unwrap() provides a std::path::PathBuf.
Invoking .to_str().unwrap() provides a reference to something that is stored inside the preceding PathBuf.
Thus, for the reference to be valid, the PathBuf must exist (and Rust enforces that).
In your first expression, the PathBuf is a temporary that will be dropped straight away; the reference home would be dangling! (it's an error).
In your second example, the home_buf binding keeps the PathBuf alive, then the reference home_str can be used as long as home_buf exists.

Another way to look at it is that your original code is roughly equivalent to:
fn main() {
let home;
{
let tmp1: Option<PathBuf> = home::home_dir();
let tmp2: PathBuf = tmp1.unwrap();
let tmp3: Option<&str> = tmp2.to_str();
home = tmp3.unwrap();
}
println!("home dir: {}", home);
}
In other words, all the temporary values created by an expression last as long as the expression, and no longer than that. Storing a reference that borrows from tmp2 into home is not allowed because, the reference would then outlive the value it borrows from.
By introducing a variable to hold the PathBuf, you made it last longer than the expression, and the reference becomes valid.

Related

trim() on .collect::<String>() E0716 in Rust

Rust code:
let item_discount_price = item_discount_price_element.text().collect::<String>().trim();
give error:
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:64:35
|
64 | let item_discount_price = item_discount_price_element.text().collect::<String>().trim();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
65 | let item_discount_price = item_discount_price.trim();
| -------------------------- borrow later used here
|
= note: consider using a `let` binding to create a longer lived valu
I have solved it with following code:
let item_discount_price = item_discount_price_element.text().collect::<String>();
let item_discount_price = item_discount_price.trim();
For background I am doing some web-scraping, item_discount_price_element is ElementRef from scraper. https://docs.rs/scraper/latest/scraper/element_ref/struct.ElementRef.html
Question is why first code is not working ?
If you look at the documentation of trim() you'll notice that it's not a member of String but of str:
pub fn trim(&self) -> &str
That is, it takes a &str (string slice) and returns another &str, a subslice, with the same lifetime.
Now, your code is equivalent of doing something like:
let s: &str = String::from(" hello ").trim();
That would create a temporary String, borrow it as a &str, and compute its trim slice. Then the temporary is dropped and the trimmed slice reference is invalidated. Fortunately Rust lifetime rules prevents you from trying to use that invalid value.
If you save a temporary value in a variable you avoid dropping it, as you noticed in your code.
let s: String = String::from(" hello ");
let s: &str = s.trim();
And now the code does what you want.
If you do not need to ever use the temporary it is idiomatic in Rust to use the same name for both values, to illustrate that point, and to avoid having to think of two names (naming things is hard). Note that the first s still exists, it is not destroyed by having another variable with the same name, it is just shadowed, and it will be dropped normally at the end of its scope.

How to move ownership of PathBuf together with Path slice [duplicate]

This question already has answers here:
Why can't I store a value and a reference to that value in the same struct?
(4 answers)
Closed 1 year ago.
I want to create a struct containing a PathBuf (with an absolute path) and a &Path (with a relative path derived from that absolute path). Here is my attempt:
use std::path::{Path, PathBuf};
struct File<'a> {
abs_path: PathBuf,
rel_path: &'a Path,
}
impl<'a> File<'a> {
fn new(abs_path: PathBuf, rel_path: &'a Path) -> File<'a> {
File { abs_path, rel_path }
}
}
fn main() {
let abs_path = PathBuf::from("/path/to/file");
let rel_path = abs_path.strip_prefix("/path").unwrap();
let _file = File::new(abs_path, rel_path);
}
When I compile this, I get the following error:
error[E0505]: cannot move out of `abs_path` because it is borrowed
--> src/main.rs:17:27
|
16 | let rel_path = abs_path.strip_prefix("/path").unwrap();
| -------- borrow of `abs_path` occurs here
17 | let _file = File::new(abs_path, rel_path);
| ^^^^^^^^ -------- borrow later used here
| |
| move out of `abs_path` occurs here
I understand that I cannot transfer ownership of abs_path because I borrowed a reference to it when I created rel_path. I am attempting to link the two together inside the struct, by restricting the lifetime of rel_path. Can anyone tell me how to get both values into the struct?
Thanks!
While there are ways to store a reference and a value together (raw pointers + Pin, Box + https://crates.io/crates/rental), the easiest thing to do would probably be to store the start and end index of the slice.

What are the best practices for multiple mutable and immutable borrows? [duplicate]

This question already has answers here:
Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?
(1 answer)
Double mutable borrow error in a loop happens even with NLL on
(2 answers)
Is it safe to logically split a borrow to work around a limitation of the NLL-enabled borrow-checker?
(2 answers)
Closed 2 years ago.
I'm developing a function that returns the content of a particular file in a Zip archive. Since I know the location of the file, I try to access it with the ZipArchive.by_name method. However, it is possible that the name of the file is written in a different case. If this happens (FileNotFound), I need to iterate over all files in the archive and perform a case-insensitive comparison with the template. However, in this case I get two errors connected with the borrowing.
Here is the minimal example (I use BarcodeScanner Android app (../test_data/simple_apks/BarcodeScanner4.0.apk) but you can use any apk file, just substitute path to it. You can download one, e.g., on ApkMirror):
use std::{error::Error, fs::File, path::Path};
const MANIFEST_MF_PATH: &str = "META-INF/MANIFEST.MF";
fn main() {
let apk_path = Path::new("../test_data/simple_apks/BarcodeScanner4.0.apk");
let _manifest_data = get_manifest_data(apk_path);
}
fn get_manifest_data(apk_path: &Path) -> Result<String, Box<dyn Error>> {
let f = File::open(apk_path)?;
let mut apk_as_archive = zip::ZipArchive::new(f)?;
let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
Ok(manifest) => manifest,
Err(zip::result::ZipError::FileNotFound) => {
let manifest_entry = apk_as_archive
.file_names()
.find(|f| f.eq_ignore_ascii_case(MANIFEST_MF_PATH));
match manifest_entry {
Some(entry) => apk_as_archive.by_name(entry).unwrap(),
None => {
return Err(Box::new(zip::result::ZipError::FileNotFound));
}
}
}
Err(err) => {
return Err(Box::new(err));
}
};
Ok(String::new()) //stub
}
Cargo.toml:
[package]
name = "multiple_borrows"
version = "0.1.0"
authors = ["Yury"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
zip = "^0.5.8"
Here are the errors:
error[E0502]: cannot borrow `apk_as_archive` as immutable because it is also borrowed as mutable
--> src/main.rs:17:34
|
14 | let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
| ----------------------------------------
| |
| mutable borrow occurs here
| a temporary with access to the mutable borrow is created here ...
...
17 | let manifest_entry = apk_as_archive
| ^^^^^^^^^^^^^^ immutable borrow occurs here
...
31 | };
| - ... and the mutable borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<ZipFile<'_>, ZipError>`
error[E0499]: cannot borrow `apk_as_archive` as mutable more than once at a time
--> src/main.rs:22:32
|
14 | let _manifest_entry = match apk_as_archive.by_name(MANIFEST_MF_PATH) {
| ----------------------------------------
| |
| first mutable borrow occurs here
| a temporary with access to the first borrow is created here ...
...
22 | Some(entry) => apk_as_archive.by_name(entry).unwrap(),
| ^^^^^^^^^^^^^^ second mutable borrow occurs here
...
31 | };
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<ZipFile<'_>, ZipError>`
I understand that these errors are connected with poor architectural decisions. What are the best practices in this case?
by_name() returns data that points inside the object that represents the archive. At the same time, it takes a &mut reference to the archive, presumably because it needs to update some internal data structures while reading. The unfortunate consequence is that you cannot call by_name() while holding on to data returned by a previous call to by_name(). In your case the arm of the match that goes on to access the archive doesn't contain references into the archive, but the borrow checker is not yet smart enough to detect that.
The usual workaround is to do it in two steps: first, determine whether the manifest file is present, and then either retrieve it by name or search for it among file_names(). In the latter case you will need to do another split, this time by cloning the name of the file before calling by_name() again. This is for the same reason: by_name() requires a mutable reference to the archive which cannot be obtained while you're holding on to the file name that refers to the data inside it. Cloning the name creates a fresh copy at a (usually negligible) run-time cost, allowing the second call to by_name().
Finally, you can combine the ok_or_else() combinator with the ? operator to simplify error propagation. With all these applied, the function could look like this:
fn get_manifest_data(apk_path: &Path) -> Result<String, Box<dyn Error>> {
let f = File::open(apk_path)?;
let mut apk_as_archive = zip::ZipArchive::new(f)?;
let not_found = matches!(
apk_as_archive.by_name(MANIFEST_MF_PATH),
Err(zip::result::ZipError::FileNotFound)
);
let _manifest_entry = if not_found {
let entry_name = apk_as_archive
.file_names()
.find(|f| f.eq_ignore_ascii_case(MANIFEST_MF_PATH))
.ok_or_else(|| zip::result::ZipError::FileNotFound)?
.to_owned();
apk_as_archive.by_name(&entry_name).unwrap()
} else {
apk_as_archive.by_name(MANIFEST_MF_PATH)?
};
Ok(String::new()) //stub
}

Insert into a HashMap based on another value in the same Hashmap

I'm trying to insert a value into a HashMap based on another value in the same HashMap, like so:
use std::collections::HashMap;
fn main() {
let mut some_map = HashMap::new();
some_map.insert("a", 1);
let some_val = some_map.get("a").unwrap();
if *some_val != 2 {
some_map.insert("b", *some_val);
}
}
which gives this warning:
warning: cannot borrow `some_map` as mutable because it is also borrowed as immutable
--> src/main.rs:10:9
|
7 | let some_val = some_map.get("a").unwrap();
| -------- immutable borrow occurs here
...
10 | some_map.insert("b", *some_val);
| ^^^^^^^^ --------- immutable borrow later used here
| |
| mutable borrow occurs here
|
= note: `#[warn(mutable_borrow_reservation_conflict)]` on by default
= warning: this borrowing pattern was not meant to be accepted, and may become a hard error in the future
= note: for more information, see issue #59159 <https://github.com/rust-lang/rust/issues/59159>
If I were instead trying to update an existing value, I could use interior mutation and RefCell, as described here.
If I were trying to insert or update a value based on itself, I could use the entry API, as described here.
I could work around the issue with cloning, but I would prefer to avoid that since the retrieved value in my actual code is somewhat complex. Will this require unsafe code?
EDIT
Since previous answer is simply false and doesn't answer the question at all, there's code which doesn't show any warning (playground)
Now it's a hashmap with Rc<_> values, and val_rc contains only a reference counter on actual data (number 1 in this case). Since it's just a counter, there's no cost of cloning it. Note though, that there's only one copy of a number exists, so if you modify a value of some_map["a"], then some_map["b"] is modified aswell, since they refer to a single piece of memory. Also note, that 1 lives on stack, so you better consider turn it into Rc<Box<_>> if you plan to add many heavy objects.
use std::collections::HashMap;
use std::rc::Rc;
fn main() {
let mut some_map = HashMap::new();
some_map.insert("a", Rc::new(1));
let val_rc = Rc::clone(some_map.get("a").unwrap());
if *val_rc != 2 {
some_map.insert("b", val_rc);
}
}
Previous version of answer
Hard to tell what exactly you're looking for, but in this particular case, if you only need to check the value, then destroy the borrowed value, before you update the hashmap. A dirty and ugly code would be like this:
fn main() {
let mut some_map = HashMap::new();
some_map.insert("a", 1);
let is_ok = false;
{
let some_val = some_map.get("a").unwrap();
is_ok = *some_val != 2;
}
if is_ok {
some_map.insert("b", *some_val);
}
}

Lack of lifetime of line from buffered reader prevents splitting line

I am banging my head trying to figure out Rust's borrowing/lifetime/ownership properties. Namely, when using a buffered reader, and attempting to split a line. The code
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let f = File::open("foo.txt").expect("file not found");
let f = BufReader::new(f);
for line in f.lines() {
let split: Vec<&str> = {
let ln: String = line.unwrap();
ln.split(' ').collect()
};
}
}
or any variation of (with or without specifying variable type, futile attempts to make it mutable, etc) results in:
'ln' does not live long enough; borrowed value must only be valid for the static lifetime...
yet trying to maybe fake an extended lifetime and grab some data out of the line via a slice
let nm = line;
name = &line[..];
or even just trying to operate the split() on the unmodified line variable results in:
cannot index into a value of type 'std::result::Result<std::string::String, std::io::Error>'
"borrowed value does not live long enough" seems to blame the wrong thing suggests that the lifetime lasts long enough to put each word into its own string, but modifying my original code on the Playground to include that nested for loop still results in a
error[E0597]: borrowed value does not live long enough
--> src/main.rs:11:18
|
11 | for w in line.unwrap().split_whitespace() {
| ^^^^^^^^^^^^^ temporary value does not live long enough
...
14 | }
| - temporary value dropped here while still borrowed
15 | }
| - temporary value needs to live until here
|
= note: consider using a `let` binding to increase its lifetime
in reference to the line.unwrap()
Ultimately, what am I misunderstanding here about Rust's lifetime or borrowing properties?
The error your original code gives when compiled is:
error[E0597]: `ln` does not live long enough
--> src/main.rs:11:13
|
11 | ln.split(' ').collect()
| ^^ borrowed value does not live long enough
12 | };
| - `ln` dropped here while still borrowed
13 | }
| - borrowed value needs to live until here
error: aborting due to previous error
As per #shepmasters comments it is a good idea to provide the full error when posting a question.
Anyway, it highlights the problem:
let split: Vec<&str> = {
let ln: String = line.unwrap();
ln.split(' ').collect()
};
You are creating a Vec containing references to str slices; slices don't own the data that they are sliced from, they are effectively pointers into data which has to be owned by another variable. Therefore the variable that they are sliced from have to outlive the slices.
Inside the expression you are using to initialise the Vec, you create a String containing the line of text that you are processing. The scope of this string is the variable ln is the initialisation expression - it will be dropped as soon as you leave that scope.
Then you split the string, which returns an iterator to string slices, one per substring. Remember though, the iterator is returning slices, which are pointers to the substrings in the String ln. Those slices are not allowed to outlive ln itself.
Hopefully you can see the problem now. As soon as you exit the initialisation expression, ln is dropped, but the Vec would still contain the str slices. What are they pointing to?
The fix is very simple. Why declare ln inside that block? In fact why have a block there at all? This works:
for line in f.lines() {
let ln: String = line.unwrap();
let split: Vec<&str> = ln.split(' ').collect();
// Now do something with split
}

Resources