My last post about my latest Rust learnings became a comparison of Rustlings and the Rust track of Exercism, and I'm not at all happy with how it turned out. But I'll leave it up anyway in case it helps someone and try to pinpoint my challenges with Rust in this one.
While continuing to do the exercises I caught myself making some weird stupid things repeatedly. For example writing
let mut ii = inputs.iter();
while let Some(x) = ii.next() {
...
}
instead of
for x in inputs.iter() {
...
}
which is much simpler and just better. I guess I'd seen so much pattern matching in code examples that I was trying to introduce a Result
or an Option
everywhere for a while. Luckily my new friend Clippy recognized the issue and I also learned to avoid it.
My pain points — the usual suspects
In all the time I've written Rust code, I think I've struggled with the same things over and over again: ownership and lifetimes (as mentioned also in the previous post).
So I decided to revisit "The Book" and refresh my memory regarding those. I noticed the link to a Brown University version with slightly modified content and occasional quizzes and gave that a try. I can warmly recommend it! The quizzes were actually rather challenging even right after I had read the topics they were related to, so I felt it definitely added to my learning.
Unfortunately that also means that even though I really felt like I had understood the concepts (again) after reading the docs, I still struggled when applying them to some real-life examples.
Wanting to really learn the language, each time I failed a quiz question or was surprised by how something worked, I tried understanding the reasons behind it. This usually helped but there were still some cases I struggled with.
For example, without trying to compile this piece of code I didn't know it won't compile. (This is a minimal example that doesn't do anything sensible.)
fn mut_vec(nums: &mut Vec<i32>) {
for _ in nums {}
nums.push(20);
}
This code fails compilation with the error 'nums' moved due to this implicit call to '.into_iter()'
pointing to the second row because of the attempt to mutate the vector after that on the third row. I was surprised by the ownership of the vector being moved when the loop is iterating through it. I believed it to be a simple read-only operation that could be done first and the vector mutated successfully after that.
Anyway, a lesson learned. I think this is something that I just have to remember in the future.
But when I tried to learn more about the issue by making the call to .into_iter()
explicit instead of implicit...
fn mut_vec(nums: &mut Vec<i32>) {
for _ in nums.into_iter() {}
nums.push(20);
}
...things got even more interesting. The above code passes compilation and — with some real logic inside the loop — also runs fine. And I don't know why. If I had to guess, I'd say it has something to do with the implicit call pointing to the function in the IntoIterator
trait while the explicit call is to the function in the vector. But this is just a stab in the dark and I have no idea if I'm on the right track.
And this is how I feel in general when doing my little Rust exercises; I'm making progress and some learnings start to stick, but pretty much every time there's also some surprising and/or annoying issue that makes the code not compile. I really like it that I get to know about such problems already when the compiler works on my code instead of things crashing in flames only when I run the code, but it can still be slightly frustrating at times.
So, it seems the road to mastering Rust will be paved with more surprises still. But then again, I guess that's what software development is like in general.