这篇文章我觉得写得挺好, 内容是关于Vec 和 struct的可变引用 #1415
suncunqian
started this conversation in
内容建议
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
https://applied-math-coding.medium.com/mutable-references-on-vectors-vs-structs-some-less-known-techniques-87098e2e2ba2
Let us start with some simple examples:
Although it looks like we are breaching Rust’s rule of not being allowed to have two mutable references on the same value, this compiles. The reason is, the mutual references do not having intersecting lifetimes. When c is defined b‘s life has ended already.
If we did this,
it would not compile, since now b is still alive when c is declared.
Let us consider next a more interesting example:
Rust will complain here of having attempted to set two mutable reference on the same value, that is the vector. But why? We actually tried to put a mutable reference not on the vector but one onto the first element and another onto the second element.
Because a vector is a contiguous region of memory, Rust is treating it as one piece when it comes to borrowing of ownership.
Let us try the same but with a struct:
All compiles and works as desired! This means we can have mutual references at the same time on different fields of a struct. In other words, we can view a struct as an assembly of variables each being able to independently claim ownership on its value.
But now it’s over:
The error occurs since the mutable reference d is borrowing the entire struct rather than only a single field. Through d we can change any arbitrary field in a. So d mutable borrows all fields at ones. As a consequence, we cannot define additional mutable references on single fields at the same time.
For the case of the vector we might could come up with an attempt like this:
But again, this won’t work because Rust is keeping a mutable reference on the entire vector as long b resp. c is alive.
The next attempt will make use of de-structuring slices. Remember, de-structuring assigns values of structured data like slices, structs, tuples and enums to new variables. Usually, de-structuring moves ownership but it also can be applied on borrowed references on such data:
It works!
The mutable slice &mut a[..] is getting de-structured into two variables first and second. Both are mutable references on the corresponding indexes of the slice.
This technique allowed us to obtain more fine grained mutable borrows of vector elements. Rust provides some similar implemented methods for this:
So, if we want to obtain mutable references one on the first element and another on the rest, we can simply do this:
What follows seems a little off scope but it will connect at the end to the previously considered concepts.
Let us consider a mutable reference on some value
This allows us to change the value in the form
But we can do more — and at first it might appear as a striking experience — we can even replace the value and obtain a mutable reference on the former value:
The method std::mem::take takes the value referred by &mut a and moves its ownership to b. Then a is left with the default value for the given type, that is, an empty vector.
The final outcome of the above is equivalent to this:
But observe the difference! The std::mem::take is doing that by only requiring a mutable reference to a‘s value. In other words, if you pass a mutable reference to a function, the function is able to replace the value owned by some variable:
A similar method is std::mem::replace:
This replaces the value owned by a with the value owned by c and returns the former value owned by a.
The amazing thing is, this works for any type and is in particular useful when dealing with mutable references. So if a is a mutable reference to some value, we can replace it with a mutable reference to some other value (or even the same):
The variable b becomes a mutable reference to the value a was referring to, and a becomes a mutable reference to the value c is owning.
Note, replace is called with a mutable reference on a mutable reference onto the underlying vector: &mut &mut Vec.
Finally, let us turn back to the first section and consider an example that combines both concepts.
Consider the struct
We aim to implement a method iter_mut on A that returns a mutable iterator of A.v. This will look like
where the returned iterator instance is the following struct:
The slice v intends to hold the remaining elements in the iteration. We need to specify a lifetime parameter since the struct is holding a reference.
The interesting part is now the implementation of Iterator on A_Iter:
When next is called, we want to return a mutable reference to the first element of self.v. In previous sections we learned that we can do this by using split_first_mut(). But note, if we would apply this directly on self.v, that is,
self.v.split_first_mut()
then this would produce a mutable reference to the first element of v although the mutable reference self.v onto the entire vector still exists. It exists as long the struct does exist. As a result, the compiler would complain.
What we have to do instead, and this is a much cleaner approach, we have to obtain the mutable reference of self.v through std::mem::replace and let self.v point to anything else (here the empty slice).
Now, we can apply split_first_mut onto the very short living mutable reference v_tmp. We obtain mutable references e and rest on the first element resp. the rest of the slice. Finally, we can return e and assign to self.v the mutable reference v_tmp.
Beta Was this translation helpful? Give feedback.
All reactions