Rust "if let" vs "unwrap" behavior - rust

I'm about a week into learning Rust, and I'm struggling to understand some of the nuances of ownership. I have this code that works:
if let Some(node) = current {
node.next = Some(Box::new(ListNode::new(sum_digit)));
current = node.next.as_mut();
}
I thought that if let Some(node) = current was equivalent to assigning node the value of current.unwrap(). However, the code below doesn't work:
current.unwrap().next = Some(Box::new(ListNode::new(sum_digit)));
current = current.unwrap().next.as_mut();
I receive the following error message:
46 | let mut current = head.as_mut();
| ----------- move occurs because `current` has type `Option<&mut Box<ListNode>>`, which does not implement the `Copy` trait
...
73 | current.unwrap().next = Some(Box::new(ListNode::new(sum_digit)));
| -------- `current` moved due to this method call
74 | current = current.unwrap().next.as_mut();
| ^^^^^^^ value used here after move
Is there a way to accomplish the same thing without using if let? I know that current is not None, so that is safe.

I thought that if let Some(node) = current was equivalent to assigning node the value of current.unwrap().
current.unwrap() will unconditionally consume current. Thanks to match ergonomics if let doesn't have to do that.
Is there a way to accomplish the same thing without using if let? I know that current is not None, so that is safe.
current.as_mut().unwrap()? You're already using as_mut.
Option::as_mut will borrow the option, and create a new option with a reference to the contents of the existing one. You can consume that (by unwrapping) without affecting the original option.

Related

Rust - How does String::from() actually work?

I'm still new to rust so there are some stuff about it I still don't know
I'm trying to split a String value and pass it to a variable like so:
let mut splitted_line = String::from("something=random").split("=");
let key = splitted_line.nth(0).expect("incorrect line format");
let value = splitted_line.nth(1).expect("incorrect line format");
The code above raise the following error:
error[E0716]: temporary value dropped while borrowed
--> src\main.rs:49:37
|
49 | let mut splitted_line = String::from("something=random").split("=");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
...
55 | let key = splitted_line.clone().nth(0).expect("incorrect line format");
| --------------------- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
Yet (from some reason I still don't know), when I run the code below it works completely fine:
let line = String::from("something=random");
let mut splitted_line = line.split("=");
let key = splitted_line.nth(0).expect("incorrect line format");
let value = splitted_line.nth(1).expect("incorrect line format");
As far as I read from the Error code description. This shouldn't have happened cause I'm not borrowing any value from a variable.
Can someone explain to me why the first code raises the temporary value error and why the second one doesn't?
split() is borrowing. It takes &self and returns an iterator that yields &strs referencing self.
The difference between the codes is when the splitted string is freed: in the first snippet it is a temporary because we invoke a method on it immediately and it is freed at the end of the statement (let mut splitted_line = String::from("something=random").split("=");). However, since we use the strings split() returned after, and they're borrowing from this freed string, this is an error.
On the second case, the string is not temporary as it is bound to a variable, and it is freed at the end of the scope. Thus, it is still alive when we use split() returned strings.

How to extend lifetime for variable handed over to tokio spawned co-routine

I'm using tokio to create a new thread to execute a task on file paths found using walkdir. It basically looks like follows:
pub async fn hash_tree(start_path: &Path) -> std::io::Result<()> {
for entry in WalkDir::new(start_path) {
let file_path_entry = entry.unwrap();
let file_path = file_path_entry.path();
let handle = tokio::spawn(async move {
hashing::hash_file(file_path).await.unwrap();
});
}
The problem with this code is that the file path doesn't live long enough. Rust fails with:
error[E0597]: `file_path_entry` does not live long enough
--> src/main.rs:44:25
|
44 | let file_path = file_path_entry.path();
| ^^^^^^^^^^^^^^^^^^^^^^
| |
| borrowed value does not live long enough
| argument requires that `file_path_entry` is borrowed for `'static`
...
61 | }
| - `file_path_entry` dropped here while still borrowed
I understand the issue but I'm not sure how to tackle it. Would it be better to first gather all the file_paths in a vector and then push them into co-routines? I would rather prefer to kick the spawned task off as soon as it is found. Can I alternatively push copies of the file_paths to a vector that is owned by the outer scope so that I can make sure that they live long enough (EDIT: tried pushing the paths onto a vector with greater scope but that didn't work either)?
What would be alternative/better ways to handle that?
Thanks Netwave and Masklinn! What finally worked was moving a clone of file_path_entry.clone(). As Masklinn pointed out, path is also borrowed. So, this works:
for entry in WalkDir::new(start_path).follow_links(false) {
let file_path_entry = entry.unwrap();
let handle = tokio::spawn(async move {
hashing::hash_file(file_path_entry.clone()).await.unwrap();
});

Programming in Rust , how to fix error[E0515] "cannot return value referencing local variable"?

Please help me to compile my code attached bellow. The compiler says that following 2 patterns depending on which lines I comment out.
The program reads a &str which is a simple "svg path command" like code then parses it. The pasted code has been simplified for simplicity. It uses Regex to split the input string into lines then study each line in the main for loop. Each loop pushes the parse result onto a vector. Finally the function returns the vector.
Basically the compiler says returning the vector is not allowed because it refers local variable. Though I don't have any workaround.
error[E0597]: `cmd` does not live long enough
--> src/main.rs:24:25
|
24 | codeV = re.captures(cmd.as_str());
| ----- ^^^ borrowed value does not live long enough
| |
| borrow might be used here, when `codeV` is dropped and runs the destructor for type `Option<regex::Captures<'_>>`
...
30 | }
| - `cmd` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0515]: cannot return value referencing local variable `cmd`
--> src/main.rs:31:1
|
24 | codeV = re.captures(cmd.as_str());
| --- `cmd` is borrowed here
...
31 | V //Error
| ^ returns a value referencing data owned by the current function
Playground
use regex::Regex;
pub fn parse(path:&str) {//->Vec<Option<regex::Captures<>>> //Error
let reg_n=Regex::new(r"\n").unwrap();
let path=reg_n.replace_all("\n"," ");
let reg_cmd=Regex::new(r"(?P<cmd>[mlhv])").unwrap();
let path=reg_cmd.replace_all(&path,"\n${cmd}");
let cmdV=reg_n.split(&path);
//let cmdV:Vec<&str> = reg.split(path).map(|x|x).collect();
let mut V:Vec<Option<regex::Captures<>>>=vec![];
let mut codeV:Option<regex::Captures<>>=None;
let mut count=0;
for cmd_f in cmdV{//This loop block has been simplified.
count+=1;
if count==1{continue;}
let mut cmd="".to_string();
cmd=cmd_f.to_string();
cmd=cmd.replace(" ","");
let re = Regex::new(r"\{(?P<code>[^\{^\}]{0,})\}").unwrap();
codeV = re.captures(cmd.as_str());
//cmd= re.replace_all(cmd.as_str(),"").to_string();
let cmd_0=cmd.chars().nth(0).unwrap();
//cmd.remove(0);
//V.push(codeV); //Compile error
V.push(None); //OK
}
//V
}
fn main() {
parse("m {abcd} l {efgh}");
}
Though I don't have any workaround.
regex's captures refer to the string they matched for efficiency. This means they can't outlive that string, as the match groups are essentially just offsets into that string.
Since the strings you match are created in the loop body, this means captures can't escape the loop body.
Aside from not creating strings in the loop body (or even the function), the solution / workaround is to convert your capture groups to owned data and store that: instead of trying to return a vector of captures, extract from the capture the data you actually want, convert it to an owned String (or tuple thereof, or whatever), and push that onto your vector.
e.g. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0107333e30f831a418d75b280e9e2f31
you can use cmd.clone().as_str() if you not sure the value has borrowed or no

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);
}
}

Resources