The robot names exercise on the Rust track requires (as far as I can tell) some pretty serious trickery. The exercise requires that Robot::new() creates robots with names which must be globally unique. I’m no expert, so there may be another way to accomplish this, but the solution I (and many others) arrived at was to use lazy_static or an unsafe block to initialize a global HashSet. I think this exercise should come with
a warning: mutable globals are ok in some cases but should be avoided if another solution is possible.
a hint: At least point out that making variable sized mutable global is awkward for a good reason and explain that reason. Then point the student at lazy_static, or better yet some material about how to initialize a mutable global HashSet using an unsafe block.
I actually like that Exercism exercises are hard enough that I have to go find solutions elsewhere on the internet, but this is a case where some direction would be helpful in my opinion.
Most the exercises on Exercism are practice exercises and do not contain any language-specific pointers or tips. Many exercises do require figuring out how to do “something”, which often is tricky and/or language-specific. The practice exercises typically do not make an attempt to lay out warnings, tricks, utilities or hints which are specific to one language.
Mentoring sessions are really good for discussion or getting additional color and context!
It’s been a couple of years since I solved this and it looks like the tests have changed. I’ve only briefly reviewed it just now, but I think when I return to pass the new test(s) I would, in a private function, populate and shuffle and return a vector and set it to a const reference binding. At most I might have a mutable atomic to advance the index that would be incremented for getting the next available name from the shuffled name pool. I don’t expect I’d have to use anything unsafe (although Rust uses a lot of unsafe under the covers, but that has hopefully been thoroughly reviewed and tested.) But it’s Rust, so the plan in my head may break on the rocks of reality.
Oh joy. Well, I have it passing the new tests locally with version 1.65.0, using thread_local! to store the vec of shuffled names inside an RWLock inside an Rc for interior mutability. It uses an AtomicBool to flag if the name pool is loaded and an AtomicUsize for the index into the name pool. But it doesn’t pass the same tests when running on the server. It might be a version thing or a test runner issue, but I think the language features I’m using have been around for a while. Anyway, it looks to me that it can be solved without laziness and without unsafe blocks, if only the test runner would understand it.
There’s more on interior mutability here. It’s basically a way that allows you to mutate data even when there are immutable references to that data.
I may submit an issue about the code passing locally and not on the server, as I don’t know anything about how the test runner works.
UPDATE
Okay, I passed more tests by also putting the Atomic variables in thread_local. One more failing test to pass…
FINAL(?) UPDATE
I had a typo in defining a range as 0..999 instead of 0..=999. That, and reverting my Rc back to an Arc seems to consistently pass the test runner now. So, using thread_local and interior mutability seems to be a good approach for shuffling all the names instead of randomly generating them and handling collision. Benchmark results for it were
test test_many_different_robots_have_different_names ... bench: 399,648 ns/iter (+/- 57,732)
I tried to benchmark a couple of exercises that use collision resolution, but the benchmarks ran a long time without reporting, maybe because of issues with clearing the HashSet between iterations of the bencher. It looks like I’m doing it right, but I suspect I’m overlooking something.