Hacker News new | ask | show | jobs
by jcelerier 1867 days ago
How would one make e.g. in Rust then ?

   #include <vector>
   int main()
   {
     std::vector<int> foo{1,2,3,4,5};
     foo.reserve(foo.size()* 2);
     for(auto it = foo.begin(), end = foo.end(); it < end; ++it)
       foo.push_back(*it);
   }
3 comments

There are a couple of options:

- Repeating the vector using the built-in method `Vec::repeat`. This allocates a new vector, but `reserve` is likely to do the same, just implicitly.

https://play.rust-lang.org/?version=stable&mode=debug&editio...

- Using a traditional for loop. There are no lifetime issues, because a new reference is acquired on each iteration. The reserve is not required, but I included it to match your C++ version. https://play.rust-lang.org/?version=stable&mode=debug&editio...

- Creating an array of slices and then concatenating them into a new Vec using the concat method on slices: https://play.rust-lang.org/?version=stable&mode=debug&editio...

The most short and elegant example I could come up with that actually worked (since the example above really shouldn't work in Rust in the first place):

    fn main() {
        let mut v = vec![1, 2, 3, 4, 5];
        let new_size = v.len()*2;
        v = v.into_iter().cycle().take(new_size).collect();
    
        println!("{:?}", v);
    }
https://play.rust-lang.org/?version=stable&mode=debug&editio...
Other comments have answered this specific question, and I think it might be interesting to look at a similar-looking question that's actually more problematic for Rust. What I'll ask is, what's the Rust equivalent of this:

    #include <vector>

    void do_stuff(int &a, int &b) {
      // stuff
    }

    int main() {
      int my_array[2] = {42, 99};
      do_stuff(my_array[0], my_array[1]);
    }
That is, how do we take two non-aliasing mutable references into the same array/vector/view/span at the same time. (To be clear, none of the following applies to shared/const references. Those are allowed to alias, and this example will just work.) Notably, the direct translation doesn't compile:

    fn main() {
        let mut my_array = [42, 99];
        do_stuff(&mut my_array[0], &mut my_array[1]);
    }
Here's the error:

    error[E0499]: cannot borrow `my_array[_]` as mutable more than once at a time
     --> src/main.rs:7:32
      |                                                                 
    7 |     do_stuff(&mut my_array[0], &mut my_array[1]);
      |     -------- ----------------  ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
      |     |        |
      |     |        first mutable borrow occurs here
      |     first borrow later used by call
      |
      = help: consider using `.split_at_mut(position)` or similar method to obtain two mutable non-overlapping sub-slices
The issue here is partly that the compiler doesn't understand how indexing works. If it understood that my_array[0] and my_array[1] were disjoint objects, it could maybe deduce that this code was legal. But then the same error would come up again if one of the indexes was non-const, so adding compiler smarts here wouldn't help the general case.

Getting multiple mutable references into a single container is tricky in Rust, because you (usually) have to statically guarantee that they don't alias, and how to do that depends on what container you're using. The suggestion in the error message is correct here, and `split_at_mut` is one of our options. Using it would look like this:

    fn main() {
        let mut my_array = [42, 99];
        let (first_slice, second_slice) = my_array.split_at_mut(1);
        do_stuff(&mut first_slice[0], &mut second_slice[0]);
    }
However, other containers like HashMap don't have `split_at_mut`, and taking two mutable references into e.g. a `HashMap<i32, i32>` would require a different approach. Refactoring our code to hold i32 keys instead of references would be the best option in that case, though it might mean paying for extra lookups. If we couldn't do that, we might have to resort to `Rc<RefCell<i32>>` or trickery with iterators. (This comment is too long already, and I'll spare the gory details.)

At a high level, Rust's attitude towards multiple-mutable-references situations is leaning in the direction of "don't do that". There are definitely ways to do it (assuming what you're doing is in fact sound, and you're not actually trying to break the aliasing rule), but many of those ways are advanced and/or not-zero-cost, and in extremis it can require unsafe code. Life in Rust is a lot easier when you refactor things to avoid needing to do this, for example with an entity-component-system sort of architecture.

Use a crate that provides safe functions implemented with unsafe code to do that, like https://docs.rs/splitmut/0.2.1/splitmut/
Neat! I bet we could add a macro to that crate to make it work with any (static) number of references. A variant of this using a HashSet to check arbitrary collections of keys might be cool too.