Another day, yet another newbie needing help with lifetimes.
I'm reading the book and trying to do a simple File walker for learning purposes, here's my code:
// The bound generic F: Read is so I can test it with io::Cursor
// instead of a real file
struct FileWalker<'a, F: Read> {
buf: String,
r: BufReader<&'a mut F>,
}
impl<'a, F: Read> FileWalker<'a, F> {
pub fn new(file: &'a mut F) -> FileWalker<F> {
FileWalker {
buf: String::new(),
r: BufReader::new(file),
}
}
fn read_line(&mut self) -> Result<&String, io::Error> {
self.buf.clear();
if self.buf.len() > MAX_BUF_READER_SIZE {
self.buf.shrink_to_fit();
}
let result = self.r.read_line(&mut self.buf);
match result {
Ok(n) => return Ok(&self.buf),
Err(e) => return Err(e),
}
}
}
impl<'a, F: Read> Iterator for FileWalker<'a, F> {
type Item = &'a String;
fn next(&mut self) -> Option<Self::Item> {
match self.read_line() {
Ok(s) => {
if s.is_empty() {
return None;
}
return Some(s);
}
Err(err) => {
// TODO - Error handling
panic!("{}", err);
}
}
}
}
The idea was that it could walk the file with a BufReader so it doesn't load the entire file at once and then call my FileWalker from another function with something like this:
pub fn find(filename: &str, _query: &str) {
let mut file = File::open(filename)
.expect("Couldn't open file");
let walker = FileWalker::new(&mut file);
for line in walker {
// Do something with each line, like matching a pattern and
// return the filepath
}
}
But it won't compile: (Click on full error below)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/File_Walker.rs:43:18
|
43 | match self.read_line() {
| ^^^^^^^^^
Full error
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/File_Walker.rs:43:18
|
43 | match self.read_line() {
| ^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 42:13...
--> src/File_Walker.rs:42:13
|
42 | fn next(&mut self) -> OptionSelf::Item {
| ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
--> src/File_Walker.rs:43:13
|
43 | match self.read_line() {
| ^^^^
note: but, the lifetime must be valid for the lifetime'a
as defined on the impl at 39:6...
--> src/File_Walker.rs:39:6
|
39 | impl<'a, F: Read> Iterator for FileWalker<'a, F> {
| ^^
note: ...so that the types are compatible
--> src/File_Walker.rs:42:46
|
42 | fn next(&mut self) -> OptionSelf::Item {
| ______________________________________________^
43 | | match self.read_line() {
44 | | Ok(s) => {
45 | | if s.is_empty() {
... |
53 | | }
54 | | }
| |_____^
= note: expectedIterator
foundIterator
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/File_Walker.rs:43:18
|
43 | match self.read_line() {
| ^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 42:13...
--> src/File_Walker.rs:42:13
|
42 | fn next(&mut self) -> OptionSelf::Item {
| ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
--> src/File_Walker.rs:43:13
|
43 | match self.read_line() {
| ^^^^
note: but, the lifetime must be valid for the lifetime'a
as defined on the impl at 39:6...
--> src/File_Walker.rs:39:6
|
39 | impl<'a, F: Read> Iterator for FileWalker<'a, F> {
| ^^
note: ...so that the types are compatible
--> src/File_Walker.rs:42:46
|
42 | fn next(&mut self) -> OptionSelf::Item {
| ______________________________________________^
43 | | match self.read_line() {
44 | | Ok(s) => {
45 | | if s.is_empty() {
... |
53 | | }
54 | | }
| |_____^
= note: expectedIterator
foundIterator
I think I kinda understand the problem (correct me if I'm wrong), my FileWalker struct has a string buffer:
struct FileWalker<'a, F: Read> {
buf: String, // <--------------------- here
r: BufReader<&'a mut F>,
}
And I'm returning a reference to it here, which could outlive FileWalker, so it needs to be annotated if I'm not mistaken:
let result = self.r.read_line(&mut self.buf);
match result {
Ok(n) => return Ok(&self.buf), // <----------- here
Err(e) => return Err(e),
}
And the borrow checker can't infer the lifetime for that reference, but how and where can I annotate it?
I could clone it but that's not how I should fix lifetime issues, right?. Another solution I can think of is to receive the string buffer from outside, e.g.: from the find() function, storing only a reference into FileWalker instead of an owned string buffer, but it defeats the purpose of having an iterable:
struct FileWalker<'a, F: Read> {
buf: &'a mut String, // <------- Change
r: BufReader<&'a mut F>,
}
impl<'a, F: Read> FileWalker<'a, F> {
pub fn new(file: &'a mut F, buf: &'a mut String) -> FileWalker<F> {
FileWalker { // ^^^^^^^^^ change
buf,
pub fn find(filename: &str, _query: &str) {
let mut file = File::open(filename)
.expect("Couldn't open file");
let mut buf = String::new(); // <---- change
let walker = FileWalker::new(&mut file, &mut buf); // <----- change
for line in walker {
// Do something with each line, like matching a pattern and
// return the filepath
}
}
That would make FileWalker a little bit pointless, wouldn't it?
I think the main problem has something to do with the structure of my concept. Maybe I'm used to abstracting in a different way of how Rust should work? How should I re-structure my code or annotate the lifetime for the ref without taking a buf from outside and without cloning?
Thanks in advance!