Failing in Rust: Creating a vector of functions

Another adventure in Rust compilation is here – this time, trying to solve the “Bob” Exercism problem.
This is a pretty simple problem to solve:

But then I thought – all those ‘if’ clauses are a bit unsightly; I can do away with them, if instead I create a list of (evaluator, reply) tuples, and then use iterators to find the first matching evaluator, and return the corresponding reply. This has the added benefit, of using my new favorite feature in Rust – Iterators (along with the unfortunate downside of being more complex, but I’m here to learn, so that’s immaterial).
So here’s my first attempt at creating and using a vector:

Which results in a long stream of errors… let’s look at the first one:

From the highlighted lines above, it appears that vec! infers the type of the “Evaluator” part of the tuple to be the concrete type of the specific closure defined at line 3 of my program. What I actually want is a generic function that takes in a string slice and returns a bool. So I changed the binding declaration to specify the concrete type of the vector:

Which retaliated with yet another barrage of errors, the first of which was:

Right – a trait is, by definition, not Sized, since it doesn’t represent a concrete object, and different objects that implement that trait may have different sizes. And why does this have to be Sized? Because std::collection::Vec requires its elements to be Sized, otherwise it would no longer support random access 1That’s probably more involved than just this, since Rust has quite a few restrictions about storing un-Sized data, but even that alone is a good enough reason why it can’t be supported in Vec, regardless of the other restrictions..
So, what do we do? Thanks to New Rustacean Ep. 05, I already know the answer to that – just Box it. So here’s my next attempt:

Things are starting to look better (compiler output fits in the console window without scrolling – Yay!):

Huh? Oh right, I’m stupid. A second look at the documentation of “map_or” shows that it takes the default value first, then the mapping function, not the other way around. Seems counter intuitive to me, but I might be odd (or maybe there’s something I’m missing). This works:

Is it prettier than my original solution? No.
Is it shorter? No.
Is it faster? I doubt it very much2It introduces heap allocations and derefrences, not to mention that it can’t be optimized by the compiler as well as the hard-coded decision tree that was there in the naïve solution..

But it was educating, so as Bob would probably put it “WORTH IT!”.


Update:
I just figured that Boxing may be a bit of an overkill here. Since the closures lifetime is the function body, we can just use basic references instead, and avoid the heap-allocation. This will yield a bit more verbose syntax:

It would have yielded nicer code if we could define the closures directly in the vector initialization, but we’ll be taking a reference to an item that has the lifetime of the vector initialization statement, which means we’ll have a reference that outlives its target.

Footnotes   [ + ]

1. That’s probably more involved than just this, since Rust has quite a few restrictions about storing un-Sized data, but even that alone is a good enough reason why it can’t be supported in Vec, regardless of the other restrictions.
2. It introduces heap allocations and derefrences, not to mention that it can’t be optimized by the compiler as well as the hard-coded decision tree that was there in the naïve solution.

2 thoughts on “Failing in Rust: Creating a vector of functions

  1. If your vector of functions isn’t changed dynamically, a very simple macro might make the code much more beautiful (and likely more performant).

    1. Good idea. And also a good excuse for me to play around with macros 🙂
      Thanks for the suggestion!

Leave a Reply

Your email address will not be published. Required fields are marked *