Hey all, pretty new to Rust. I've been working through the book, and when I read the chapter on trait objects I was itching to go back and change the minigrep
project to use a generic Iterator<Item=&'a str>
type rather than the concrete Vec<&'a str>
of the examples.
(Why? I like the C#/Clojure—and maybe Haskell?—style of passing lazy iterators around. It fits in better with my functional thinking, personally, and calling .collect()
all the time nags me.)
At any rate, I've got the following code, and I can't quite identify why it won't compile—I know Rust thinks the lifetime should be 'static
, but I can't see why, how to fix it, or why it's the .lines()
line that is identified as the issue. I read a similar post, but adding + 'a
only makes the error message longer.
I've not yet converted the run
function to return a boxed iterator, so it still calls .collect()
. I've omitted the tests. I've given some additional code at the top of the file that I don't think is representative of the issue, but may be nice if you want to drop this in a file and run rustc
or cargo build
on it.
I've included the error message afterwards.
Please let me know if additional details would help resolve the problem. I'm not looking for the solution to get things done , but rather an explanation so I can learn from this.
use std::error::*;
use std::fs;
#[derive(Debug)]
pub struct Config {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
impl Config {
pub fn new(mut args: std::env::Args, case_sensitive: bool) -> Result<Config, String> {
args.next(); // eat the program name
args.next()
.map_or(Err(String::from("missing query")), |query| {
args.next()
.map_or(Err(String::from("missing filename")), |filename| {
Ok(Config {
query,
filename,
case_sensitive,
})
})
})
}
}
pub fn run(conf: Config) -> Result<Vec<String>, Box<dyn Error>> {
let contents = fs::read_to_string(&conf.filename)?;
let search_fn = if conf.case_sensitive {
search
} else {
search_case_insensitive
};
Ok(search_fn(&conf.query, &contents)
.enumerate()
.map(|(num, line)| format!("{}:{}:{}", conf.filename, num, line))
.collect())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Box<dyn Iterator<Item = &'a str>> {
search_backend(query, contents, |x| x.to_string())
}
pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Box<dyn Iterator<Item = &'a str>> {
search_backend(&query.to_lowercase(), contents, |line| line.to_lowercase())
}
fn search_backend<'a, F>(
query: &str,
contents: &'a str,
line_pp: F,
) -> Box<dyn Iterator<Item = &'a str>>
where
F: Fn(&&str) -> String,
{
Box::new(
contents
.lines()
.filter(|line| line_pp(line).contains(&query)),
)
}
// tests omitted
Compiling minigrep v0.1.0 (/Users/Knoble/code/rust/minigrep)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/lib.rs:62:14
|
62 | .lines()
| ^^^^^
|
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the function body at 52:19...
--> src/lib.rs:52:19
|
52 | fn search_backend<'a, F>(
| ^^
note: ...so that reference does not outlive borrowed content
--> src/lib.rs:61:9
|
61 | contents
| ^^^^^^^^
= note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable
--> src/lib.rs:60:5
|
60 | / Box::new(
61 | | contents
62 | | .lines()
63 | | .filter(|line| line_pp(line).contains(&query)),
64 | | )
| |_____^
= note: expected `std::boxed::Box<(dyn std::iter::Iterator<Item = &'a str> + 'static)>`
found `std::boxed::Box<dyn std::iter::Iterator<Item = &str>>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0495`.
error: could not compile `minigrep`.
To learn more, run the command again with --verbose.