this post was submitted on 10 Apr 2024
32 points (100.0% liked)

Rust

5974 readers
80 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

!performance@programming.dev

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 1 year ago
MODERATORS
 

I just had a random thought: a common pattern in Rust is to things such as:

let vec_a: Vec<String> = /* ... */;
let vec_b: Vec<String> = vec_a.into_iter().filter(some_filter).collect();

Usually, we need to be aware of the fact that Iterator::collect() allocates for the container we are collecting into. But in the snippet above, we've consumed a container of the same type. And since Rust has full ownership of the vector, in theory the memory allocated by vec_a could be reused to store the collected results of vec_b, meaning everything could be done in-place and no additional allocation is necessary.

It's a highly specific optimization though, so I wonder if such a thing has been implemented in the Rust compiler. Anybody who has an idea about this?

you are viewing a single comment's thread
view the rest of the comments
[–] arendjr@programming.dev 5 points 7 months ago (8 children)

I mean, the actual operation is just an example, of course. Feel free to make it a .map() operation instead. The strings couldn’t be reused then, but the vector’s allocation still could… in theory.

[–] myotherself@lemmings.world 7 points 7 months ago* (last edited 7 months ago) (7 children)

map() can still be used with Vec::iter_mut(), filter_map() can be replaced with Vec::retain_mut().

[–] arendjr@programming.dev 4 points 7 months ago* (last edited 7 months ago) (6 children)

Yeah, that's helpful if I would be currently optimizing a hot loop now. But I was really just using it as an example. Also, retain_mut() doesn't compose as well.

I'd much rather write:

let vec_a: Vec<String> = /* ... */;
let vec_b: Vec<String> = vec_a
    .into_iter()
    .filter(some_filter)
    .map(some_map_fn)
    .collect();

Over:

let mut vec_a: Vec<String> = /* ... */;
vec_a.retain_mut(|x| if some_filter(x) {
    *x = some_map_fn(*x); // Yikes, cannot move out of reference.
    true
} else {
    false
});

And it would be nice if that would be optimized the same. After all, the point of Rust's iterators is to provide zero-cost abstractions. In my opinion, functions like retain_mut() represent a leakiness to that abstraction, because the alternative turns out to not be zero cost.

[–] myotherself@lemmings.world 1 points 7 months ago

To be fair, these alternatives are also limited to the case where the item type stays the same.

load more comments (5 replies)
load more comments (5 replies)
load more comments (5 replies)