Scope of 'let' with shadowing and String -> &str conversion - rust

With the following code, I tried to return &str of temperature of user input, but in vain. Then, I am trying to return f32, but still struggle...
Q1. The reason I am getting the error at the bottom is because the scope of 'let temp = String::new();' still persists, even I 'shadow' it later by 'let temp = temp.trim().parse::<f32>();' within the loop?
Q2. How can I rewrite the code so that it returns &str?
fn gettemp() -> f32 {
let temp = String::new();
loop {
println!("What is your temperature?");
io::stdin().read_line(&mut temp).expect("Failed to read the line");
let temp = temp.trim().parse::<f32>();
if !temp.is_ok() {
println!("Not a number!");
} else {
break;
}
}
temp
}
Error:
error[E0308]: mismatched types
--> src/main.rs:70:5
|
49 | fn gettemp() -> f32 {
| --- expected `f32` because of return type
...
70 | temp
| ^^^^ expected f32, found struct `std::string::String`
|
= note: expected type `f32`
found type `std::string::String`

A1 - nope, that's not how shadowing works. Let's look at your code with comments.
fn gettemp() -> f32 {
let temp = String::new(); // Outer
loop {
// There's no inner temp at this point, even in the second
// loop pass, etc.
println!("What is your temperature?");
// Here temp refers to the outer one (outside of the loop)
io::stdin().read_line(&mut temp).expect("Failed to read the line");
// Shadowed temp = let's call it inner temp
let temp = temp.trim().parse::<f32>();
// ^ ^
// | |- Outer temp
// |- New inner temp
// temp refers to inner temp
if !temp.is_ok() {
println!("Not a number!");
} else {
// Inner temp goes out of scope
break;
}
// Inner temp goes out of scope
}
// Here temp refers to outer one (String)
temp
}
A2 - you can't return &str. #E_net4 posted a link to the answer why. However, you can return String. You can do something like this nn case you'd like to have a validated String:
fn gettemp() -> String {
loop {
println!("What is your temperature?");
let mut temp = String::new();
io::stdin()
.read_line(&mut temp)
.expect("Failed to read the line");
let trimmed = temp.trim();
match trimmed.parse::<f32>() {
Ok(_) => return trimmed.to_string(),
Err(_) => println!("Not a number!"),
};
}
}
I see couple of another problems in your code.
let temp = String::new();
Should be let mut temp, because you'd like to borrow mutable reference later (&mut temp in the read_line call).
Another issue is the loop & read_line. read_line appends to the String. Run this code ...
let mut temp = "foo".to_string();
io::stdin().read_line(&mut temp).unwrap();
println!("->{}<-", temp);
... and enter 10 for example. You'll see following output ...
->foo10
<-
... which is not what you want. I'd rewrite gettemp() in this way:
fn gettemp() -> f32 {
loop {
println!("What is your temperature?");
let mut temp = String::new();
io::stdin()
.read_line(&mut temp)
.expect("Failed to read the line");
match temp.trim().parse() {
Ok(temp) => return temp,
Err(_) => println!("Not a number!"),
};
}
}
IMHO explicit return temp is much cleaner & readable (compared to suggested break out of the loop with a value).
A3 - Why we don't need to explicitly state <f32> in temp.trim().parse()
It's inferred by the compiler.
fn gettemp() -> f32 { // 1. f32 is return type
loop {
println!("What is your temperature?");
let mut temp = String::new();
io::stdin()
.read_line(&mut temp)
.expect("Failed to read the line");
match temp.trim().parse() {
// 4. parse signature is pub fn parse<F>(&self) -> Result<F, ...>
// compiler knows it must be Result<f32, ...>
// Result<f32, ...> = Result<F, ...> => F = f32
// F was inferred and there's no need to explicitly state it
Ok(temp) => return temp,
// | |
// | 2. return type is f32, temp must be f32
// |
// | 3. temp must be f32, the parse result must be Result<f32, ...>
Err(_) => println!("Not a number!"),
};
}
}

Regarding question 1, you can break out of the loop with a value:
fn gettemp() -> f32 {
let mut temp = String::new();
loop {
println!("What is your temperature?");
io::stdin().read_line(&mut temp).expect("Failed to read the line");
let temp = temp.trim().parse::<f32>();
if !temp.is_ok() {
println!("Not a number!");
} else {
break temp.unwrap() // yield value when breaking out of loop
}
}
}
This way, the whole loop's value is the thing you passed along with break.
Regarding question 2, I am not sure if you really want to do this, because &str is a borrowed type. I think you want to return an String in this case which owns the data.

In your program, loop { ... } creates a new scope. The scope of the second temp starts where it's defined and ends when loop ends. See the following example:
fn main() {
let a = 1;
{
let a = 2;
println!("{}", a);
}
println!("{}", a);
}
This prints 2, 1。
If you want to return a string, use (the code is fixed according to the comment below):
fn gettemp() -> String {
loop {
let mut temp = String::new();
println!("What is your temperature?");
std::io::stdin().read_line(&mut temp).expect("Failed to read the line");
temp = temp.trim().to_string();
match temp.parse::<f32>() {
Err(_) => println!("Not a number!"),
_ => return temp,
}
}
}
&str is a borrowed reference. You cannot return a borrowed reference to a local variable which will be released when the function returns.

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 fix this "borrowed value does not live long enough" error?

I'm writing a program that detects duplicate frames in h264 encoded video. I'm using the ac-ffmpeg crate Here's my code:
use std::fs::File;
use ac_ffmpeg::{
codec::{
video::{frame::Planes, VideoDecoder},
CodecParameters, Decoder,
},
format::{
demuxer::{Demuxer, DemuxerWithStreamInfo},
io::IO,
muxer::{Muxer, OutputFormat},
},
Error,
};
fn open_input(path: &str) -> Result<DemuxerWithStreamInfo<File>, Error> {
let input = File::open(path)
.map_err(|err| Error::new(format!("unable to open input file {}: {}", path, err)))?;
let io = IO::from_seekable_read_stream(input);
Demuxer::builder()
.build(io)?
.find_stream_info(None)
.map_err(|(_, err)| err)
}
fn open_output(path: &str, elementary_streams: &[CodecParameters]) -> Result<Muxer<File>, Error> {
let output_format = OutputFormat::guess_from_file_name(path)
.ok_or_else(|| Error::new(format!("unable to guess output format for file: {}", path)))?;
let output = File::create(path)
.map_err(|err| Error::new(format!("unable to create output file {}: {}", path, err)))?;
let io = IO::from_seekable_write_stream(output);
let mut muxer_builder = Muxer::builder();
for codec_parameters in elementary_streams {
muxer_builder.add_stream(codec_parameters)?;
}
muxer_builder.build(io, output_format)
}
fn plane_difference(planes1: &Planes, planes2: &Planes) -> u64 {
6
}
fn remove_duplicate_frames(input: &str, output: &str) -> Result<(), Error> {
let mut demuxer = open_input(input)?;
let (stream_index, (stream, _)) = demuxer
.streams()
.iter()
.map(|stream| (stream, stream.codec_parameters()))
.enumerate()
.find(|(_, (_, params))| params.is_video_codec())
.ok_or_else(|| Error::new("no video stream"))?;
let mut decoder = VideoDecoder::from_stream(stream)?.build()?;
let mut packet_count = 0;
let mut prev_planes: Option<Planes> = None;
let mut diffs = Vec::<(i64, u64)>::new();
while let Some(packet) = demuxer.take()? {
if packet.stream_index() != stream_index {
continue;
}
decoder.push(packet.clone())?;
if let Some(frame) = decoder.take()? {
let planes = frame.planes();
match prev_planes {
Some(prev) => {
let diff = plane_difference(&planes, &prev);
diffs.push((packet.dts().timestamp() / 1000, diff));
}
None => (),
};
prev_planes = Some(planes);
}
if packet_count > 2000 {
break;
}
packet_count += 1;
}
diffs.sort_by(|a, b| b.1.cmp(&a.1));
dbg!(diffs);
Ok(())
}
with dependency ac-ffmpeg = "0.17.4".
The problem is this gives an error that frame does not live long enough:
error[E0597]: `frame` does not live long enough
--> src/main.rs:106:26
|
106 | let planes = frame.planes();
| ^^^^^^^^^^^^^^ borrowed value does not live long enough
107 |
108 | match prev_planes {
| ----------- borrow later used here
...
117 | }
| - `frame` dropped here while still borrowed
My understanding of this error is that frame is borrowed, which via planes ends up in prev_planes. But at the end of every loop the value is dropped so it doesn't exist for the next iteration. Is that correct? And how can I solve that? I tried cloning various things in this code but I'm not able to fix the error.
VideoFrame doesn't allow you to take the planes, it only allows you to borrow them from the frame.
You can see that because a lifetime is attached to the planes:
pub fn planes(&self) -> Planes<'_>
The '_ lifetime says it's identical to the lifetime of the &self reference, meaning it is borrowed from self.
If you want to store the planes, store the frame that contains them instead:
use std::fs::File;
use ac_ffmpeg::{
codec::{
video::{frame::Planes, VideoDecoder, VideoFrame},
CodecParameters, Decoder,
},
format::{
demuxer::{Demuxer, DemuxerWithStreamInfo},
io::IO,
muxer::{Muxer, OutputFormat},
},
Error,
};
fn open_input(path: &str) -> Result<DemuxerWithStreamInfo<File>, Error> {
let input = File::open(path)
.map_err(|err| Error::new(format!("unable to open input file {}: {}", path, err)))?;
let io = IO::from_seekable_read_stream(input);
Demuxer::builder()
.build(io)?
.find_stream_info(None)
.map_err(|(_, err)| err)
}
fn open_output(path: &str, elementary_streams: &[CodecParameters]) -> Result<Muxer<File>, Error> {
let output_format = OutputFormat::guess_from_file_name(path)
.ok_or_else(|| Error::new(format!("unable to guess output format for file: {}", path)))?;
let output = File::create(path)
.map_err(|err| Error::new(format!("unable to create output file {}: {}", path, err)))?;
let io = IO::from_seekable_write_stream(output);
let mut muxer_builder = Muxer::builder();
for codec_parameters in elementary_streams {
muxer_builder.add_stream(codec_parameters)?;
}
muxer_builder.build(io, output_format)
}
fn plane_difference(planes1: &Planes, planes2: &Planes) -> u64 {
6
}
fn remove_duplicate_frames(input: &str, output: &str) -> Result<(), Error> {
let mut demuxer = open_input(input)?;
let (stream_index, (stream, _)) = demuxer
.streams()
.iter()
.map(|stream| (stream, stream.codec_parameters()))
.enumerate()
.find(|(_, (_, params))| params.is_video_codec())
.ok_or_else(|| Error::new("no video stream"))?;
let mut decoder = VideoDecoder::from_stream(stream)?.build()?;
let mut packet_count = 0;
let mut prev_frame: Option<VideoFrame> = None;
let mut diffs = Vec::<(i64, u64)>::new();
while let Some(packet) = demuxer.take()? {
if packet.stream_index() != stream_index {
continue;
}
decoder.push(packet.clone())?;
if let Some(frame) = decoder.take()? {
let planes = frame.planes();
match prev_frame {
Some(prev) => {
let diff = plane_difference(&planes, &prev.planes());
diffs.push((packet.dts().timestamp() / 1000, diff));
}
None => (),
};
prev_frame = Some(frame);
}
if packet_count > 2000 {
break;
}
packet_count += 1;
}
diffs.sort_by(|a, b| b.1.cmp(&a.1));
dbg!(diffs);
Ok(())
}
You need to keep the frame itself from one loop iteration to the next instead of the planes. So have a prev_frame variable instead of prev_planes:
let mut prev_frame = None;
let mut diffs = Vec::<(i64, u64)>::new();
while let Some(packet) = demuxer.take()? {
if packet.stream_index() != stream_index {
continue;
}
decoder.push(packet.clone())?;
if let Some(frame) = decoder.take()? {
let planes = frame.planes();
match prev_frame {
Some(prev) => {
let diff = plane_difference(&planes, &prev.planes());
diffs.push((packet.dts().timestamp() / 1000, diff));
}
None => (),
};
prev_frame = Some(frame);
}
}
It's a bit tricky to reproduce this, so I'll try to answer from the top of my head.
Did you try:
let planes = frame.planes().to_owned();
Mainly you're giving ownership of planes (i.e. the child), while the parent (frame) goes out of scope.
Why to_owned? (if the child is a ref, clone will return a ref, so we use to_owned instead since it converts a ref type to an owned type where it's allowed to transfer ownership.
The solution should be either:
let planes = frame.planes().to_owned(); // line 106
or
prev_planes = Some(planes.clone()); // line 116
If none of these work, try updating your question with their outputs. It'll make it easier to answer.

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

"Borrowed value does not live long enough", dropped when used in a loop

I understand that the String is dropped when the scope of the loop ends and that the vector input contains slices of trimmed_text.
I suppose the resolution is to move the ownership of those slices to input or something like that. How can this be done?
use std::io;
fn main() {
let mut input: Vec<&str>;
loop {
let mut input_text = String::new();
println!("Type instruction in the format Add <name> to <department>:");
io::stdin()
.read_line(&mut input_text)
.expect("failed to read from stdin");
let trimmed_text: String = input_text.trim().to_string();
input = trimmed_text.split(" ").collect();
if input[0] == "Add" && input[2] == "to" {
break;
} else {
println!("Invalid format.");
}
}
println!("{:?}", input);
}
The compile error:
error[E0597]: `trimmed_text` does not live long enough
--> src/main.rs:14:17
|
14 | input = trimmed_text.split(" ").collect();
| ^^^^^^^^^^^^ borrowed value does not live long enough
...
21 | }
| - `trimmed_text` dropped here while still borrowed
22 |
23 | println!("{:?}", input);
| ----- borrow later used here
.split() returns references to a String which is dropped by the end of the loop, but you want input to live past the end of the loop, so you should refactor it to hold owned values instead of references. Example:
use std::io;
fn example() {
let mut input: Vec<String>; // changed from &str to String
loop {
let mut input_text = String::new();
println!("Type instruction in the format Add <name> to <department>:");
io::stdin()
.read_line(&mut input_text)
.expect("failed to read from stdin");
// map str refs into owned Strings
input = input_text.trim().split(" ").map(String::from).collect();
if input[0] == "Add" && input[2] == "to" {
break;
} else {
println!("Invalid format.");
}
}
println!("{:?}", input);
}
playground
As you did understand, the problem is that the owner of the slices doesn't live long enough. What you didn't point out (and maybe did not explicitly see) is that the owner is the variable trimmed_text. So what you want to do (if you don't want to copy each slice to have better performances) is to make trimmed_text's scope bigger:
use std::io;
fn main() {
let mut input: Vec<&str>;
let mut trimmed_text: String;
loop {
...
trimmed_text = input_text.trim().to_string();
input = trimmed_text.split(" ").collect();
if input[0] == "Add" && input[2] == "to" {
break;
} else {
...
}
}
println!("{:?}", input);
}
Here, we resolved the error of the lifetime of the owner of the slices. However, we have a second issue:
13 | trimmed_text = input_text.trim().to_string();
| ^^^^^^^^^^^^ assignment to borrowed `trimmed_text` occurs here
14 |
15 | input = trimmed_text.split(" ").collect();
| ----- ------------ borrow of `trimmed_text` occurs here
| |
| borrow might be used here, when `input` is dropped and runs the `Drop` code for type `std::vec::Vec`
This tells us that after a loop, the input variable still borrows trimmed_text as it is modified. To fix this, we can reduce the scope of input so that input's scope doesn't contain the line 13 :
use std::io;
fn main() {
// Remove the "let mut input: Vec<&str>;"
let mut trimmed_text: String;
loop {
...
trimmed_text = input_text.trim().to_string();
let input: Vec<&str> = trimmed_text.split(" ").collect();
...
}
}
If we don't use input outside of the loop, this works just fine. Now we just need to output the value of input at the end of the loop :
use std::io;
fn main() {
// Remove the "let mut input: Vec<&str>;"
let mut trimmed_text: String;
let results = loop {
...
trimmed_text = input_text.trim().to_string();
let input: Vec<&str> = trimmed_text.split(" ").collect();
if input[0] == "Add" && input[2] == "to" {
break input;
}
...
};
println!("{:?}", results);
}
And here we have it !
TL;DR:
Here is a code that does what you want without duplicating the slices :
use std::io;
fn main() {
let mut trimmed_text: String;
let results = loop {
let mut input_text = String::new();
println!("Type instruction in the format Add <name> to <department>:");
io::stdin()
.read_line(&mut input_text)
.expect("failed to read from stdin");
trimmed_text = input_text.trim().to_string();
let input: Vec<&str> = trimmed_text.split(" ").collect();
if input[0] == "Add" && input[2] == "to" {
break input;
} else {
println!("Invalid format.");
}
};
println!("{:?}", results);
}
I renamed input to results to avoid confusion with the input variable inside the loop. Feel free to rename it back to input if you really want to.

Cannot find the position of a string within a file because the BufReader is used after it's moved

I'm trying to create a function that returns the position of a string within a file. This is my code:
use std::{
fs::File,
io::{BufRead, BufReader, Seek, SeekFrom},
};
fn find(log_file: &str, key: &str) -> Option<u64> {
let log = File::open(log_file).unwrap();
let mut reader = BufReader::new(log);
for line in reader.lines() {
let line = line.unwrap();
if line.starts_with(&format!("{},", key)) {
let bytes = line.as_bytes();
let current_offset = reader.seek(SeekFrom::Current(0)).unwrap();
return Some(current_offset - bytes.len() as u64);
}
}
None
}
playground
When I attempt to compile I get:
error[E0382]: use of moved value: `reader`
--> src/main.rs:13:34
|
9 | for line in reader.lines() {
| ------ value moved here
...
13 | let current_offset = reader.seek(SeekFrom::Current(0)).unwrap();
| ^^^^^^ value used here after move
|
= note: move occurs because `reader` has type `std::io::BufReader<std::fs::File>`, which does not implement the `Copy` trait
Is there any way I can make this work?
This question is not a duplicate of Cannot use moved BufReader after for loop with bufreader.lines() because using by_ref doesn't work either because then the compiler tells me that reader is borrowed twice mutably.
I think you should use .read_line() here. This is more efficient than calling .lines()
use std::{
fs::File,
io::{BufRead, BufReader},
};
fn find(log_file: &str, key: &str) -> Option<u64> {
let log = File::open(log_file).unwrap();
let mut reader = BufReader::new(log);
let mut line = String::new();
let mut pos = 0;
while let Ok(num) = reader.read_line(&mut line) {
if num == 0 {
break;
}
if line.starts_with(&format!("{},", key)) {
return Some(pos);
}
pos += num as u64;
line.clear();
}
None
}

Resources