Reputation: 3118
A database API gives us the option to build a Filter
object that will be passed onto a Query
.
It offers a fluent API to build such Filter
:
filter
.topic0(topic0)
.topic1(topic1)
.topic2(topic2)
.topic3(topic3)
The topics are pre-organised into a Vec<Option<String>>
, which we can pass onto the filters obj, using the helper function:
fn add_topics(mut f: &Filter, topics: Vec<String>) {
if let Some(topic0) = topics.get(0) {
f = &f.topic0(topic0);
}
if let Some(topic1) = topics.get(1) {
f = &f.topic1(topic1);
}
if let Some(topic2) = topics.get(2) {
f = &f.topic2(topic2);
}
if let Some(topic3) = topics.get(3) {
f = &f.topic3(topic3);
}
}
Unfortunately, the db API doesn't offer a .topics()
method that accepts a Vec<_>
.
Still, is there any way to avoid duplication of the logic?
E.g. I'm proficient with JS/TS, in which the above can be written as:
const addTopics = (filter: Filter, topics: string[]) {
for (let i = 0; i < topics.length; ++i) {
if (topics[i]) {
filter = filter['topic' + i](topics[i]);
}
}
}
Rust, as a typed language, doesn't allow this as far as I know.
Is there any other way?
Upvotes: 0
Views: 123
Reputation: 299703
By a minimal reproducible example, Herohtar means something like:
struct Filter {}
impl Filter {
fn topic0(&self, topic: &str) -> &Self {
self
}
fn topic1(&self, topic: &str) -> &Self {
self
}
fn topic2(&self, topic: &str) -> &Self {
self
}
fn topic3(&self, topic: &str) -> &Self {
self
}
}
fn main() {
let topic0 = "";
let topic1 = "";
let topic2 = "";
let topic3 = "";
let filter = Filter {};
let f = filter
.topic0(topic0)
.topic1(topic1)
.topic2(topic2)
.topic3(topic3);
}
This abstracts away all of the details, while giving a clear example of what you mean. (If what I've written isn't what you mean, then that's the point of writing a minimal example.)
To build the function you're looking for, you need a way to map what method you want to call to each location. That's fairly straightforward, although it requires a somewhat advanced type:
fn add_topics(mut f: &Filter, topics: Vec<Option<String>>) {
// Fancy type because of the borrows that need a lifetime.
// It's just the signature for a method call. The `for` allows
// adding a lifetime.
type TopicAssigner = for<'a> fn(&'a Filter, &str) -> &'a Filter;
// Now, match up the methods in the order of the Vector
let assign_topic: Vec<TopicAssigner> = vec![
Filter::topic0,
Filter::topic1,
Filter::topic2,
Filter::topic3,
];
// And zip it with what you're passed
for (topic, assigner) in zip(topics, assign_topic) {
if let Some(topic) = topic {
f = (assigner)(f, &topic);
}
}
}
Note that this has a problem if topics has more values than are expected, so it would be nice to force it into a 4-element array instead:
fn add_topics(mut f: &Filter, topics: [Option<String>; 4]) {
But perhaps that's inconvenient for other reasons.
Upvotes: 2