Yes and we could replace all of that with an implementation in assembly which both compiles
and runs faster.
But we don't do that because for the entire history of computing, we've come to realize that abstractions enable us to reason about programs at a higher level without having to be bogged down in irrelevant details and that getting those details right once lets us avoid the inevitable bugs that occur while reimplementing them thousands of times.
By absolutely every objective measure we know in software engineering
let triples = ints.map(|x| x * 3)
is strictly superior to
var triples = []int
for value := range ints {
triples = append(triples, value * 8)
}
The fact that this even has to be argued is just bonkers to me.
The second one is trivial to introduce bugs into, reading it requires mentally filtering out 90% of the code as irrelevant minutiae, and it's even worse from a performance perspective unless you remember each and every time to allocate a result array of the appropriate length to avoid reallocation. None of the details of allocating a result array or iterating over indices/values are important from the purpose of expressing the problem to be solved, and yet every time someone has to read this solution those things are of greater visibility than the actual business logic.
Worse, repetitive and unnecessary boilerplate like this hides bugs. Did you catch the fact that I'm multiplying by the wrong value in the golang version? Did you catch that I'm accidentally tripling the indices and not the values themselves? If you did, would you at least acknowledge that the first bug is much easier to identify in the first example than the second, and that the second bug is strictly impossible?
As an added bonus, the first one (in Rust at least) is automatically vectorized using SIMD for optimal performance. This happens even as you chain additional operations to arbitrary complexity.
If you still somehow think the second version is "better", then whatever your argument is can trivially be repurposed to say this version is better still:
int* triples = malloc(sizeof(int) * array_len);
for (int i = 0; i < array_len; i++) {
triples[i] = array[i] * 3;
}
If those details of creating a result array and explicitly enumerating are important, why isn't maintaining your own counter and directly indexing? At least the C version somewhat encourages you to allocate a result array of the correct size.