Confusion about temporary values - rust

I am writing a small function that reads /proc/bus/input/devices line by line and looks for a pattern in each block.
let mut handlers = Vec::<&str>::new();
let entry = RefCell::new(String::new());
let re = Regex::new(r"(?m)(event\d+)").unwrap();
for line in lines {
let l = line.unwrap();
if !l.is_empty() {
entry.borrow_mut().push_str(&l);
continue
}
if entry.borrow().contains("EV=120013") {
if let Some(captures) = re.captures(entry.borrow().as_str().clone()) {
if let Some(m) = captures.get(0) {
&handlers.push(m.as_str());
}
}
}
entry.borrow_mut().clear();
}
However, the build fails with the following error:
error[E0716]: temporary value dropped while borrowed
--> src/linux/keylogger.rs:50:53
|
50 | if let Some(captures) = re.captures(entry.borrow().as_str().clone()) {
| ^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
51 | if let Some(m) = captures.get(0) {
52 | &handlers.push(m.as_str());
| ------------------------- borrow later used here
...
55 | }
| - temporary value is freed at the end of this statement
|
= note: consider using a `let` binding to create a longer lived value
I tried to create such binding, but I couldn't get it to work ...

entry.borrow() returns a so called guard. You have to keep this guard alive for the entire time you access the content.
As you are only using it inline, it goes out of scope right away and the borrowed value gets returned.
You need to store it in a local variable that stays alive for as long as you need the borrowed content.
That's only the first problem, though. The second one is that you can't you can't store &str that aren't &'static str in a vector. &str do not keep the content of the string alive, they are only references. To store them somewhere, you need the owning version, String.
use std::cell::RefCell;
use regex::Regex;
fn main() {
let lines = [Some("aaa"), Some("bbb")];
let mut handlers = Vec::<String>::new();
let entry = RefCell::new(String::new());
let re = Regex::new(r"(?m)(event\d+)").unwrap();
for line in lines {
let l = line.unwrap();
if !l.is_empty() {
entry.borrow_mut().push_str(&l);
continue;
}
if entry.borrow().contains("EV=120013") {
let entry_guard = entry.borrow();
if let Some(captures) = re.captures(&entry_guard) {
if let Some(m) = captures.get(0) {
handlers.push(m.as_str().to_string());
}
}
}
entry.borrow_mut().clear();
}
}

Related

How do I tackle lifetimes in Rust?

I am having issues with the concept of lifetimes in rust. I am trying to use the crate bgpkit_parser to read in a bz2 file via url link and then create a radix trie.
One field extracted from the file is the AS Path which I have named path in my code within the build_routetable function. I am having trouble as to why rust does not like let origin = clean_path.last() which takes the last element in the vector.
fn as_parser(element: &BgpElem) -> Vec<u32> {
let x = &element.as_path.as_ref().unwrap().segments[0];
let mut as_vec = &Vec::new();
let mut as_path: Vec<u32> = Vec::new();
if let AsPathSegment::AsSequence(value) = x {
as_vec = value;
}
for i in as_vec {
as_path.push(i.asn);
}
return as_path;
}
fn prefix_parser(element: &BgpElem) -> String {
let subnet_id = element.prefix.prefix.ip().to_string().to_owned();
let prefix_id = element.prefix.prefix.prefix().to_string().to_owned();
let prefix = format!("{}/{}", subnet_id, prefix_id);//.as_str();
return prefix;
}
fn get_aspath(raw_aspath: Vec<u32>) -> Vec<u32> {
let mut as_path = Vec::new();
for i in raw_aspath {
if i < 64511 {
if as_path.contains(&i) {
continue;
}
else {
as_path.push(i);
}
}
else if 65535 < i && i < 4000000000 {
if as_path.contains(&i) {
continue;
}
else {
as_path.push(i);
}
}
}
return as_path;
}
fn build_routetable(mut trie4: Trie<String, Option<&u32>>, mut trie6: Trie<String, Option<&u32>>) {
let url: &str = "http://archive.routeviews.org/route-views.chile/\
bgpdata/2022.06/RIBS/rib.20220601.0000.bz2";
let parser = BgpkitParser::new(url).unwrap();
let mut count = 0;
for elem in parser {
if elem.elem_type == bgpkit_parser::ElemType::ANNOUNCE {
let record_timestamp = &elem.timestamp;
let record_type = "A";
let peer = &elem.peer_ip;
let prefix = prefix_parser(&elem);
let path = as_parser(&elem);
let clean_path = get_aspath(path);
// Issue is on the below line
// `clean_path` does not live long enough
// borrowed value does not live long
// enough rustc E0597
// main.rs(103, 9): `clean_path` dropped
// here while still borrowed
// main.rs(77, 91): let's call the
// lifetime of this reference `'1`
// main.rs(92, 17): argument requires
// that `clean_path` is borrowed for `'1`
let origin = clean_path.last(); //issue line
if prefix.contains(":") {
trie6.insert(prefix, origin);
}
else {
trie4.insert(prefix, origin);
}
count+=1;
if count >= 10000 {
println!("{:?} | {:?} | {:?} | {:?} | {:?}",
record_type, record_timestamp, peer, prefix, path);
count=0
}
};
}
println!("Trie4 size: {:?} prefixes", trie4.len());
println!("Trie6 size: {:?} prefixes", trie6.len());
}
Short answer: you're "inserting" a reference. But what's being referenced doesn't outlive what it's being inserted into.
Longer: The hint is your trie4 argument, the signature of which is this:
mut trie4: Trie<String, Option<&u32>>
So that lives beyond the length of the loop where things are declared. This is all in the loop:
let origin = clean_path.last(); //issue line
if prefix.contains(":") {
trie6.insert(prefix, origin);
}
While origin is a Vec<u32> and that's fine, the insert method is no doubt taking a String and either an Option<&u32> or a &u32. Obviously a key/value pair. But here's your problem: the value has to live as long as the collection, but your value is the last element contained in the Vec<u32>, which goes away! So you can't put something into it that will not live as long as the "container" object! Rust has just saved you from dangling references (just like it's supposed to).
Basically, your containers should be Trie<String, Option<u32>> without the reference, and then this'll all just work fine. Your problem is that the elements are references, and not just contained regular values, and given the size of what you're containing, it's actually smaller to contain a u32 than a reference (pointer size (though actually, it'll likely be the same either way, because alignment issues)).
Also of note: trie4 and trie6 will both be gone at the end of this function call, because they were moved into this function (not references or mutable references). I hope that's what you want.

How to iteratively call a function with ref argument and update state?

This is a minimal example. I have a function foo that takes a reference of a vector and returns a brand new vector. Then, there's another function bar that iteratively calls foo and update its state. But I cannot get it to compile.
fn foo(input: &Vec<i32>) -> Vec<i32> {
let len = input.len();
return input[0..len-1].to_vec();
}
fn bar() {
let input = vec![1,2,3,4,5];
let mut out = &input;
for _ in 0..2 {
out = foo(out);
}
}
Gives this error:
78 | out = foo(out);
| ^^^^^^^^
| |
| expected `&Vec<i32>`, found struct `Vec`
| help: consider borrowing here: `&foo(out)`
So that makes sense since out has a mismatched type of &Vec<i32>. Now, if I add an ampersand in front of the foo(out) call, then I got a different error:
78 | out = &foo(out);
| ^^^^---^- temporary value is freed at the end of this statement
| | |
| | borrow later used here
| creates a temporary which is freed while still in use
What is the right way to fix this?
The clean way, if possible, is to consistently hold onto the actual value instead of a reference:
fn bar() {
let input = vec![1,2,3,4,5];
let mut out = input;
for _ in 0..2 {
out = foo(&out);
}
}
or, equivalently,
fn bar() {
let input = vec![1,2,3,4,5];
let out = (0..2).fold(input, |prev, _| foo(&prev));
}
But if you need to continue using input afterwards, then you’ll have to find some other way to make the return value of foo live longer than the loop, e.g.
fn bar() {
let input = vec![1,2,3,4,5];
let mut out_storage;
let mut out = &input;
for _ in 0..2 {
out_storage = foo(out);
out = &out_storage;
}
}

How to fix use of moved value in Rust?

I am trying to convert a yaml file to xml using Rust and I am not able to figure out how to fix this error regarding the use of moved value. I think I understand why this error is coming, but haven't got a clue about what to do next.
Here's the code:
struct Element {
element_name: String,
indentation_count: i16,
}
struct Attribute<'a> {
attribute_name: &'a str,
attribute_value: &'a str,
}
fn convert_yaml_to_xml(content: String, indentation_count: i16) -> String {
let mut xml_elements: Vec<Element> = vec![];
let mut attributes: Vec<Attribute> = vec![];
xml_elements.push(Element {element_name: "xmlRoot".to_string(), indentation_count: -1});
let mut target: Vec<u8> = Vec::new();
let mut xml_data_writer = EmitterConfig::new().perform_indent(true).create_writer(&mut target);
let mut attribute_written_flag = false;
let mut xml_event;
xml_event = XmlEvent::start_element("xmlRoot");
for line in content.lines() {
let current_line = line.trim();
let caps = indentation_count_regex.captures(current_line).unwrap();
let current_indentation_count = caps.get(1).unwrap().as_str().to_string().len() as i16;
if ELEMENT_REGEX.is_match(current_line) {
loop {
let current_attribute_option = attributes.pop();
match current_attribute_option {
Some(current_attribute_option) => {
xml_event.attr(current_attribute_option.attribute_name, current_attribute_option.attribute_value)
},
None => {
break;
},
};
}
xml_data_writer.write(xml_event);
// Checking if the line is an element
let caps = ELEMENT_REGEX.captures(current_line).unwrap();
let element_name = caps.get(2);
let xml_element_struct = Element {
indentation_count: current_indentation_count,
element_name: element_name.unwrap().as_str().to_string(),
};
xml_elements.push(xml_element_struct);
xml_event = XmlEvent::start_element(element_name.unwrap().as_str());
attribute_written_flag = false;
} else if ATTR_REGEX.is_match(current_line) {
// Checking if the line is an attribute
let caps = ATTR_REGEX.captures(current_line).unwrap();
let attr_name = caps.get(2);
let attr_value = caps.get(3);
// Saving attributes to a stack
attributes.push(Attribute{ attribute_name: attr_name.unwrap().as_str(), attribute_value: attr_value.unwrap().as_str() });
// xml_event.attr(attr_name.unwrap().as_str(), attr_value.unwrap().as_str());
}/* else if NEW_ATTR_SET_REGEX.is_match(current_line) {
let caps = NEW_ATTR_SET_REGEX.captures(current_line).unwrap();
let new_attr_set_name = caps.get(2);
let new_attr_set_value = caps.get(3);
current_xml_hash.insert("name".to_string(), new_attr_set_name.unwrap().as_str().to_string());
current_xml_hash.insert("value".to_string(), new_attr_set_value.unwrap().as_str().to_string());
} */
}
if attribute_written_flag {
xml_data_writer.write(xml_event);
}
for item in xml_elements.iter() {
let event = XmlEvent::end_element();
let event_name = item.element_name.to_string();
xml_data_writer.write(event.name(event_name.as_str()));
}
println!("OUTPUT");
println!("{:?}", target);
return "".to_string();
}
And here's the error:
error[E0382]: use of moved value: `xml_event`
--> src/main.rs:77:25
|
65 | let mut xml_event;
| ------------- move occurs because `xml_event` has type `StartElementBuilder<'_>`, which does not implement the `Copy` trait
...
77 | xml_event.attr(current_attribute_option.attribute_name, current_attribute_option.attribute_value)
| ^^^^^^^^^ --------------------------------------------------------------------------------------- `xml_event` moved due to this method call, in previous iteration of loop
|
note: this function takes ownership of the receiver `self`, which moves `xml_event`
--> /Users/defiant/.cargo/registry/src/github.com-1ecc6299db9ec823/xml-rs-0.8.4/src/writer/events.rs:193:24
|
193 | pub fn attr<N>(mut self, name: N, value: &'a str) -> StartElementBuilder<'a>
| ^^^^
From XmlEvent::start_element() documentation we see that it produces a StartElementBuilder<'a>.
From StartElementBuilder<'a>::attr() documentation we see that it consumes the StartElementBuilder<'a> (the first parameter is self, not &mut self) and produces a new StartElementBuilder<'a> (which is probably similar to self but considers the expected effect of .attr()).
This approach is known as the consuming builder pattern, which is used in Rust (for example std::thread::Builder).
The typical usage of such an approach consists in chaining the function calls: something.a().b().c().d() such as something is consumed by a(), its result is consumed by b(), the same about c() and finally d() does something useful with the last result.
The alternative would be to use mutable borrows in order to modify in place something but dealing with mutable borrows is known as difficult in some situations.
In your case, you can just reassign the result of .attr() to xml_event because otherwise the .attr() function would have no effect (its result is discarded) and xml_event would become unusable because it is consumed; reassigning it makes it usable again afterwards (at least i guess, i didn't try).

How can I iterate on a Bevy Query and keep a reference to the iterated value so that I can use it later?

I have a borrow in the empty variable and I want to extend its life. In the commented code-block, I attempt to address it, but the reference is no longer available. I have to loop through the loop again to find the match in order to act on it.
How can I loop through a query looking for a best-match and then act on it once I know it's the best match, without having to loop through to find it again?
use bevy::prelude::*;
struct Person;
struct Name(String);
fn main() {
App::build()
.add_default_plugins()
.add_startup_system(startup.system())
.add_system(boot.system())
.run();
}
fn boot(mut query: Query<(&Person, &mut Name)>) {
let mut temp_str = String::new();
let mut empty: Option<&mut Name> = None;
for (_p, mut n_val) in &mut query.iter() {
if n_val.0.to_lowercase() > temp_str.to_lowercase() {
temp_str = n_val.0.clone();
empty = Some(&mut n_val);
}
}
println!("{}", temp_str);
if let Some(n) = empty {
// ...
}
// for (_p, mut n_val) in &mut query.iter() {
// if n_val.0 == temp_str {
// n_val.0 = "a".to_string();
// }
// }
}
fn startup(mut commands: Commands) {
commands
.spawn((Person, Name("Gene".to_string())))
.spawn((Person, Name("Candace".to_string())))
.spawn((Person, Name("Zany".to_string())))
.spawn((Person, Name("Sarah".to_string())))
.spawn((Person, Name("Carl".to_string())))
.spawn((Person, Name("Robert".to_string())));
}
Cargo.toml:
[package]
name = "sample"
version = "0.1.0"
authors = [""]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = "0.1.3"
Specific error:
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:17:33
|
17 | for (_p, mut n_val) in &mut query.iter() {
| ^^^^^^^^^^^-
| | |
| | temporary value is freed at the end of this statement
| creates a temporary which is freed while still in use
...
24 | if let Some(n) = empty {
| ----- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
error[E0597]: `n_val` does not live long enough
--> src/main.rs:20:26
|
20 | empty = Some(&mut n_val);
| ^^^^^^^^^^ borrowed value does not live long enough
21 | }
22 | }
| - `n_val` dropped here while still borrowed
23 | println!("{}", temp_str);
24 | if let Some(n) = empty {
| ----- borrow later used here
You cannot extend the lifetime of a reference, but that is not your issue here, the error says temporary value dropped while borrowed, so you must extend the lifetime of your temporary.
If you wonder what temporary, the compiler also points to that (literally) in the error message: query.iter(). This is a function call, and the returned value is not bound to anything, so the compiler creates a temporary value for that. Then you iterate using a reference to that temporary. When the for loop ends, the temporary is dropped and any reference lifetime to it expires.
The solution is to bind the temporary to a local variable. This way you extend the lifetime of the object to the scope of the variable:
let mut iter = query.iter();
for (_p, n_val) in &mut iter {
if n_val.0.to_lowercase() > temp_str.to_lowercase() {
temp_str = n_val.0.clone();
empty = Some(n_val);
}
}
PS: I find quite bizarre the pattern of iterating over &mut iter. I would expect the return of iter() to implement Iterator or IntoIterator directly, but it looks like this is not the case.

How can I convert a temporary String to a &str? [duplicate]

This question already has answers here:
How do I make format! return a &str from a conditional expression?
(3 answers)
Closed 3 years ago.
I wish to convert a String created using the format! macro to a &str and assign this to a value using a let binding:
fn main() {
let my_bool = true;
let other = String::from("my_string");
let result = if my_bool {
format!("_{}", other).as_str()
} else {
"other"
};
println!("{}", result);
}
(Rust Playground)
When I do this, the compiler complains that the temporary String value is freed at the end of the statement (from what I've understood), meaning that I'm unable to dynamically create a &str:
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:5:9
|
4 | let result = if my_bool {
| ------ borrow later stored here
5 | format!("_{}", other).as_str()
| ^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
6 | } else {
| - temporary value is freed at the end of this statement
|
I've been trying to understand Rust's lifetime system, but I can't really wrap my head around this one. Rust suggests the following:
= note: consider using a `let` binding to create a longer lived value
I wrapped format!("_{}", other) in a let binding:
fn main() {
let my_bool = true;
let other = String::from("my_string");
let result = if my_bool {
let s = format!("_{}", other);
s.as_str()
} else {
"other"
};
println!("{}", result);
}
But it doesn't seem to solve the issue, as when I call as_str() on this binding it still complains that the borrowed value does not live long enough:
error[E0597]: `s` does not live long enough
--> src/main.rs:6:9
|
4 | let result = if my_bool {
| ------ borrow later stored here
5 | let s = format!("_{}", other);
6 | s.as_str()
| ^ borrowed value does not live long enough
7 | } else {
| - `s` dropped here while still borrowed
This works when I omit the whole if, but I would rather not do this as this would cause a lot of headaches in the original code base.
Also, that does seem like kind of a cop-out, because then I still wouldn't know why this fails.
How would I go about solving this in a systematic manner?
&str is a borrowed string, so you cannot get one from a temporary String, otherwise a reference would outlive the value it is bound to.
There are 2 solutions to palliate this problem:
You can create a new variable to store the String out of the if scope:
fn main() {
let my_bool = true;
let other = String::from("my_string");
let result;
let result = if my_bool {
result = format!("_{}", other);
result.as_str()
} else {
"other"
};
println!("{}", result);
}
You can use the Cow type to do what you want:
use std::borrow::Cow;
fn main() {
let my_bool = true;
let other = String::from("my_string");
let result = if my_bool {
Cow::Owned(format!("_{}", other))
} else {
Cow::Borrowed("other")
};
assert_eq!("_my_string", result);
}
Cow (for clone on write) is an enum that have either an owned or a borrowed data. In this particular case, result has the type Cow<str>.
You can simplify the notation by writing:
let result = if my_bool {
format!("_{}", other).into()
} else {
Cow::Borrowed("other")
};
or (matter of style):
let result: Cow<str> = if my_bool {
format!("_{}", other).into()
} else {
"other".into()
};

Resources