I'm on chapter 12 of The Rust Programming Language, where a case insensitive line search is implemented. It doesn't make sense to me to implement the same logic twice, so I figured if I just called the case sensitive search function with the parameters converted to lower case, that might work. It did not.
This is my non working code:
fn main() {
let a = search("Waldo", "where in\nthe world\nis Waldo?");
let b = search("waldo", "where in\nthe world\nis Waldo?");
let c = search_case_insensitive("waldo", "where in\nthe world\nis Waldo?");
println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
let contents2: &str = &contents.to_lowercase();
search(&query, contents2)
}
The error in most versions I've come up with is inevitably something very much like:
error[E0597]: borrowed value does not live long enough
--> src/main.rs:25:28
|
25 | let contents2: &str = &contents.to_lowercase();
| ^^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
...
28 | }
| - temporary value only lives until here
|
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 23:1...
--> src/main.rs:23:1
|
23 | pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
EDIT 2:
Since you've updated the question with an MCVE and you've stated you don't care about straying away from the book examples... here is another version relying on extra allocations via the use of String
:
fn main() {
let a = search("Waldo", "where in\nthe world\nis Waldo?");
let b = search("waldo", "where in\nthe world\nis Waldo?");
let c = search_case_insensitive("waldo", "where in\nthe world\nis Waldo?");
println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
}
pub fn search<S>(query: S, contents: S) -> Vec<String> where S: Into<String> {
let query = query.into();
let mut results = Vec::new();
for line in contents.into().lines() {
if line.contains(&query) {
results.push(line.into());
}
}
results
}
pub fn search_case_insensitive<S>(query: S, contents: S) -> Vec<String> where S: Into<String> {
let query = query.into().to_lowercase();
let contents = contents.into().to_lowercase();
search(query, contents)
}
Here it is running in the Playground
EDIT:
I realised I never really gave you an alternative. Here's what I would probably do:
pub enum SearchOptions {
CaseSensitive,
CaseInsensitive
}
pub fn search<'a>(query: &str, contents: &'a str, options: SearchOptions) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
let check = match options {
SearchOptions::CaseSensitive => line.contains(query),
SearchOptions::CaseInsensitive => line.to_lowercase().contains(&query.to_lowercase()),
};
if check {
results.push(line);
}
}
results
}
This is about as far as you could get "de-dupe"'ing it.
Original answer:
The actual problem is that you're trying to pass the contents
around when its bound to the lifetime 'a
... but what you really want to be "case insensitive" is the query
.
This isn't bound to the lifetime 'a
in quite the same way and as such ... works:
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
search(&query, contents)
}
Here it is on the playground
You'll need to still duplicate the logic though ... because you need to match the lowercase query with the lowercase line ... which is demonstrated in the examples in the book:
if line.to_lowercase().contains(&query) {
// ^^^^^^^^^^^^^^ each LINE is converted to lowercase here in the insensitive search
results.push(line);
}
"How do I stop duplicating the logic?" - well they're not quite the same in the first place. I think your attempt wasn't quite what you were after in the first place (happy to be corrected though).
You have introduced an impossible constraint on the lifetime of variable contents2
; by writing &'a
you are attempting to assign to it the same lifetime as to the contents
argument, but it is created and destroyed within the scope of search_case_insensitive
and thus is outlived by contents
.
In order for contents2
to outlive the body of search_case_insensitive
you would need to either return it as a String
and assign to some variable outside of it or pass it to search_case_insensitive
by reference as long as it already exists as a String
elsewhere.
Citing The Book:
It's important to understand that lifetime annotations are
descriptive, not prescriptive. This means that how long a reference is
valid is determined by the code, not by the annotations.