2

In Python, if I have a list and want to add 10 to each element I would do:

bar = [2,4,5,6,7]
bar = [x + 10 for x in bar]

resulting in: [12,14,15,16,17]. How can this be done in Rust? Is the only way doing a for loop and going through each vector element?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Miguel
  • 1,295
  • 12
  • 25

2 Answers2

4

The Rust way to do this is very similar to Python: use iterators! The rough equivalent to Python's list comprehension is iter::map to get the new elements, and iter::collect to collect into a new vector (or some other kind of collection).

So for example, if bar is a Vec<i32> (or any other primitive integer type) and you want to add 10 to each element, try

bar = bar.into_iter().map(|x| x + 10).collect();

(playground)

Alternatively, you could mutate the elements in-place with

bar.iter_mut().for_each(|x| *x += 10);

(playground)

This is basically like a for loop, but a bit more succinct. This is generally going to be more efficient than the first method since you don't need to allocate a new vector (a sufficiently smart compiler may be able to avoid this). The only downside is that this version is less flexible. The output still needs to be a vector; you couldn't switch to a hash set or what have you. You also wouldn't be able to keep a copy of the old vector. See below for some examples of what's possible.

fn main() {
    let mut bar = vec![2, 4, 5, 6, 7];
    // Overwrite the old vector
    bar = bar.into_iter().map(|x| x + 10).collect();
    println!("new bar: {:?}", bar);

    let bar = vec![2, 4, 5, 6, 7];
    // Make a completely new vector
    // Note that this works only because i32 implements the Copy trait,
    // so we can make copies of the elements of bar without any problems
    // In more general situations, we may need to clone each element
    let foo: Vec<_> = bar.iter().map(|&x| x + 10).collect();
    println!("old bar: {:?} (it's still around)", bar);
    println!("new foo: {:?}", foo);

    use std::collections::HashSet;
    let bar = vec![2, 4, 5, 6, 7];
    // transform the data and collect it into a HashSet
    // instead of a vector
    let bar: HashSet<_> = bar.into_iter().map(|x| x + 10).collect();
    println!("new bar: {:?} (note that now bar is unordered)", bar);

    let mut bar = vec![2, 4, 5, 6, 7];
    // Overwrite the old vector in place
    bar.iter_mut().for_each(|x| *x += 10);
    println!("new bar: {:?}", bar);
}

(playground)

SCappella
  • 9,534
  • 1
  • 26
  • 35
  • Thank you, I just did not understand a part of the answer. When you say "The only downside is that this version is less flexible. The output still needs to be a vector; you couldn't switch to a hash set or what have you. " What do you mean by being less flexible and the output needs to be a vector? What would be the advantage of the hashSet method if the objective is to always work with a vector? – Miguel Oct 11 '19 at 17:52
  • 1
    @Miguel Sometimes the objective isn't to always work with a vector. Or even when you're still working with vectors, if the type of the underlying data changes, you'll need a brand new vector. For example, if `strings` is a vector of type `Vec`, then you wouldn't be able to do `strings.for_each().map(|s| *s = s.len())` since that would change the type of each string and the vector as a whole. – SCappella Oct 11 '19 at 18:04
0

This is basic code which illustrates how to do it the way the question assumes by default. It might be useful for beginners with Rust like me:

fn increment_mut(p: &mut Vec<i32>, to_add: i32){
    for i in 0..p.len() {
        p[i] += to_add;
    }
}

fn main() {
    let mut p = vec![2, 4, 5, 6, 7];
    increment_mut(&mut p, 10);

    // Print the complete vector in Debug.
    println!("{:?}", p)
}
$ cargo run
[12, 14, 15, 16, 17]

Using iter_mut

fn increment_mut2(p: &mut Vec<i32>, to_add: i32) {
    for x in p.iter_mut() {
        *x += to_add;
    }
}

fn main() {
    let mut p = vec![2, 4, 5, 6, 7];
    increment_mut2(&mut p, 10);

    // Print the complete vector in Debug.
    println!("{:?}", p)
}
$ cargo run
[12, 14, 15, 16, 17]
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Vladir Parrado Cruz
  • 2,301
  • 21
  • 27
  • 1
    When possible, iterators are preferred. Direct index access can incur overhead due to checking for out-of-bounds access. `&mut Vec<_>` is non-idiomatic ([Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?](https://stackoverflow.com/q/40006219/155423)). There are self-modifying operators (`+=`). `p.iter()` is usually written as `&`. It's not needed to dereference in the printing for loop ([Does println! borrow or own the variable?](https://stackoverflow.com/q/30450399/155423)). – Shepmaster Jul 06 '20 at 18:24
  • Thanks a lot for your suggestions and revision @Shepmaster, I will dig into this and try to improve this code. – Vladir Parrado Cruz Jul 06 '20 at 18:31