Reputation: 74
Consider this simple example: Having v: Vec<Vec<i32>>
I want to add v[1]
to v[0]
.
I'm not even considering sacrificing performance, so cloning any of the vectors is not an option. Thus, no matter how exactly we are going to implement the addition of the vectors, we would need simultaneous references inside v
: &mut v[0]
and &v[1]
. Obviously the problem here is that indexing borrows v
and indexing in mutable context borrows v
mutably so the borrow checker would not allow this.
This example leads to a more general problem: how can we hold simultaneous references to different elements of a container if all methods that return (mutable) references to elements (mutably) borrow the container itself?
Note that I do understand the origin of the problem and why in compile time the borrow checker cannot see that we are referencing different elements. The question is how do we tell the compiler we are doing the right thing without sacrificing performance and/or safety?
Currently I'm aware of 3 possible solutions without a noticeable performance overhead:
slice::split_at_mut
is a decent workaround but unfortunately works only for sequential/slice-like containers. Note that this function utilizes unsafe code in the implementation.map: HashMap<i32, Vec<i32>>
and I again want to add map[1]
to map[0]
. Note that (to my knowledge) iterators also use unsafe code (directly or indirectly).RefCell<T>
, that is, wrapping elements of containers in a RefCell
. (Well, or Cell<T>
in some cases). There are two problems with this one though. The first is the slight performance overhead for runtime borrow checking. The second one is that if, for example, I'm writing a function which returns a container, I would either have to make the caller use my RefCell
-wrapped container or have the whole container essentially copied in order to remove the RefCell
wrapping (Is there a way to unwrap a RefCell
inside a container for free?) And yet again, RefCell
uses unsafe code.All of these solutions use unsafe code. Is it really the only way? I'm also quite sure there is a solution that uses unsafe code directly, but as a beginner I'm a little afraid to delve into unsafe Rust yet. Though, if it happens to be a good solution, please point me to the topics I would need to study.
Are there any other solutions and which one is more practical? Please correct me wherever I don't understand something or understand it wrong.
Edit: As Sven Marnach pointed out, my question is too broad, so I'm narrowing the problem down.
I have a map: HashMap<i32, Vec<i32>>
and I want to assign map[0] + map[1]
(element-wise addition) to map[0]
with zero performance overhead. Here, repetitive indexing as suggested by matiu would not be optimal because it would involve multiple searches on the same key. So, is it possible? If not, what is the best solution for this case?
Upvotes: 2
Views: 426
Reputation: 7725
So the goal is to overwrite vec[0] with vec[0] + vec[1] ?
I think the trick is to use indexes into the Vec, instead of holding the references open.
Does this meet the goal ?
fn main() {
let mut vec = vec![
vec![1, 2, 3],
vec![10, 20, 30],
];
let ln = vec[0].len();
for i in 0..ln {
vec[0][i] += vec[1][i];
}
println!("{:?}", vec);
}
I also wanted to test if doing it in place was actually faster than creating a new array. "Perhaps the compiler is smart enough to do internal memory re-use" I thought. Turns out the compiler is not that smart.
The for loop with indexes is the fastest way.
Results (on my laptop):
running 7 tests
test bench_create_new_array ... bench: 230 ns/iter (+/- 0)
test bench_for_indexes ... bench: 174 ns/iter (+/- 0)
test bench_new_array_borrow ... bench: 231 ns/iter (+/- 0)
test bench_to_owned1 ... bench: 1,097 ns/iter (+/- 4)
test bench_to_owned_in_place ... bench: 240 ns/iter (+/- 1)
test bench_to_owned_in_place2 ... bench: 1,080 ns/iter (+/- 159)
test bench_to_owned_in_place3 ... bench: 1,037 ns/iter (+/- 2)
Upvotes: 2