Reputation: 1336
First of all, note that I have seen How to filter a vector of custom structs? already, however, it doesn't contain the solution to my issue (or at least I can't see one). The main difference is that in my filter I try to use contain() method of a string. It is better if I show it with an example.
Here are my structs:
#[derive(Clone)]
pub struct Spell {
pub title: String,
pub code: String,
pub category: String,
pub sub_category: String,
pub tool: String,
pub file_name: String,
}
#[derive(Debug)]
pub struct SearchParameters {
pub category: String,
pub sub_category: String,
pub tool: String,
pub search_str: String,
}
And here's the function I try to process the vector in:
pub fn search_spell(&self, search_parameters: SearchParameters) -> Vec<Spell> {
let tmp_results: Vec<Spell> = self
._spell_list
.clone()
.into_iter()
.filter(|spell| {
if search_parameters.category.is_empty() {
true
} else {
*spell.category == search_parameters.category
}
})
.filter(|spell| {
if search_parameters.sub_category.is_empty() {
true
} else {
*spell.sub_category == search_parameters.sub_category
}
})
.filter(|spell| {
if search_parameters.tool.is_empty() {
true
} else {
*spell.tool == search_parameters.tool
}
})
.filter(|spell| {
if search_parameters.search_str.is_empty() {
true
} else {
//*spell.title.contains(search_parameters.search_str)
true
}
})
.collect();
// TODO: replace this part by including in the filer above
let mut results: Vec<Spell> = Vec::new();
for spell in tmp_results {
if search_parameters.search_str.is_empty() {
results.push(spell);
} else {
if spell
.title
.to_lowercase()
.contains(&search_parameters.search_str.to_lowercase())
{
results.push(spell);
}
}
}
results
}
The issue is with the last filter block (see the commented code), where instead of ==
comparison, I would like to use .contains()
method. The issue is that at the compile time, the compiler can't be sure what this value actually is.
For now, I have resolved this by processing the vector in the loop at the end of this method, but it feels a little bit forced if you know what I mean. I really think I should be able to resolve this somehow in the filter.
Upvotes: 2
Views: 918
Reputation: 169018
There's a few things to fix up here. Let's go over them one by one.
The first is the issue you're having. The problem is that Pattern
is only implemented for &str
but you're trying to give it String
. This is solved easily enough by borrowing the String
as a slice -- just prefix the whole expression with &
(which you do in your for
loop).
However, invoking to_lowercase()
in the filter
closure is wasteful: the search_str
value doesn't change, but you'll perform the conversion and allocation for each item in the original collection. Instead, invoke to_lowercase()
outside of the filter
closure and move the result into the closure.
Finally, contains()
returns a bool
so the *
is an error. Dereferencing a bool
makes no sense; just omit the *
to return the bool
.
Also, you clone the entire Vec<Spell>
and then filter that, which is also wasteful. You clone items that may never be produced. Instead, consider this method chain, which only clones the items that are actually produced:
self._spell_list
.iter()
.filter(...)
.filter(...)
.cloned()
.collect();
Even better, have the method return impl Iterator<Item=&'_ Spell>
and avoid cloning altogether! If the caller needs to clone the items, they can, otherwise they can use the borrowed Spell
s without needing to clone.
To make returning an iterator of references work, we need to destructure the SearchParameters
and move each element into the respective closure, or combine the various filter
s into one single filter
, or we need to borrow SearchParameters
from the caller instead of taking it by value, but borrowing it is a good change as well: the caller can reuse SearchParameters
values multiple times, since this function doesn't need to take ownership of it.
Finally, we can make the logic of each filter
more terse by applying the transformation: if x { true } else { y }
becomes x || y
.
Putting all these changes together:
pub fn search_spell<'a>(&'a self, search_parameters: &'a SearchParameters)
-> impl Iterator<Item=&'a Spell>
{
let search_str = search_parameters.search_str.to_lowercase();
self
._spell_list
.iter()
.filter(|spell| {
search_parameters.category.is_empty() ||
*spell.category == search_parameters.category
})
.filter(|spell| {
search_parameters.sub_category.is_empty() ||
*spell.sub_category == search_parameters.sub_category
})
.filter(|spell| {
search_parameters.tool.is_empty() ||
*spell.tool == search_parameters.tool
})
.filter(move |spell| {
search_str.is_empty() ||
spell.title.contains(&search_str)
})
}
Upvotes: 2