I'm trying to get the value just before an index from an array, and handle the case where the index is at the beginning by returning zero instead. These are the things I've tried so far, both of which work:
let prev = if i == 0 { 0 } else { values[i - 1] };
let prev = i.checked_sub(1).map(|i| values[i]).unwrap_or(0);
They both feel kind of ugly to me, but nothing else comes to mind that makes it cleaner. What is the idiomatic way of solving this problem?
I think the second version is more expressive. I like that subtraction and checking happens in the same operation (checked_sub) instead of i - 1 relying on a previously checked condition. Using if let could perhaps be more readable because you can give a better name to the index variable:
let prev = if let Some(previous_index) = i.checked_sub(1) {
values[previous_index]
} else {
0
};
values.get(i - 1).unwrap_or(0) would be perfectly idiomatic if it worked, but sadly it doesn't because indexing uses usize.
I've noticed before, usually in the context of iterators, that there seems to be a divide between those who find chains-of-combinators-like-expressions intuitive or elegant, and those who find them puzzling or obtuse.
I think you might be running it in a strange environment, I know weird things have been known to happen in webassembly when trying to handle overflows. I can assure you that it definitely does work. playground
I think my favourite solution is @drewkett's, (matching on checked sub) because it uses easy to read constructs, isn't unnecessarily verbose, and avoids operations that can panic (except if the index is too big, but that's a whole different thing). @Zicog's is also pretty good (matching on the index), but I agree with @Riateche about how it's nice to put the checking and subtraction in the same operation, so for that reason it feels marginally less safe. (what if for some strange reason I needed to change it from -1 to -2?)
Rust as a language seems to prefer chains of methods to imparative code, which has its pros and cons. In any other language, my second option would seem pretty ridiculous, but I felt like it fits well with that paradigm so I had to try it. Thanks to @juggle-tux for suggesting map_or, I'd never heard of it before and it's a good improvement to what I came up with.
It looks like this solution is the best in terms of least complicated abstractions:
and this one is best if you like the method chaining pattern:
In a final, desperate, attempt to banish all confusing, abstract, verbose, high level conceptual baggage of combintor chains, match clauses, and even if, I had a shot at tackling this problem the good old C and assembler programmer "branchless" way.
After all we all know the new fangled high level constructs introduce bloat to the executable and branching with if hinders performance with unnecessary branch instructions and the consequent flushing of CPU pipelines.
This then is the ultimate, HLL concept free, clearest way to get the job done:
let prev = (0 - ((i >= 1) as u32)) & values[(0 - ((i > 1) as usize)) & (i - 1)]
Please take a moment to marvel at it's simplicity. It would be simpler if Rust would let me do unary minus and didn't insist on those redundant as. In the absence of any branches this has to be a performance demon.
Except.... This actually generates more instructions than most other solutions here. Especially on the "happy path" where the given index is greater than zero.