39 ms·
So how do you avoid it?
by creshal 10y ago
So how do you avoid it?
- nikbackm 10y agoUse unsafe or wait for the improved borrow-checker?
- nercury 10y agoWell, you either uniquely mutate, or keep the value immutable. Usually you construct the pipeline to switch between these as you need. If you need to share the result from one mutation to the other, you can do it by passing a simple immutable value that communicates the state change to other mutation, but don't try to do both at the same time. This is simply a good practice enforced at compile time. Of course, sometimes these rules prevent some valid cases, but overall it is worth it. If we are talking about HashMaps, the Rust std lib has useful Entry api to for get-or-insert use cases. In rare cases where you need to share the value between different parts of the system, there are primitives you can wrap your value in and enable reference counting, as well as internal mutation (even if wrapper is immutable, it provides safe api for mutation). In cases where the concurrency is needed, there are mutexes, channels, and multiple libraries to parallelize the code (like rayon or crossbeam).
- AstralStorm 10y agoThe problem is there is nothing like "atomic mutable", equivalent of C++ std::atomic with at least sequential consistency. So you get to drop all safety often instead to have shared mutability.
- dbaupp 10y agoThis is wrong. There is a std::sync::atomic module, along with types like Mutex and RWLock.
- burntsushi 10y agoThere's no single solution to the problem, which is perhaps one reason why folks new to Rust might stumble over it. You can't just get two aliased mutable pointers to the same region of memory in safe code. Sometimes the borrow checker can't quite see through everything. Consider this trivial example, which will fail the borrow checker: let mut owned = vec![1, 2]; let x = &mut owned[0]; let y = &mut owned[1]; That this fails might come as a surprise to a lot of folks because it's easy for a human to see that it's perfectly safe. Namely, neither `x` nor `y` refer to the same point in memory, but the borrow checker still forbids it. What's the solution to this problem? Well, it depends on what problem you're trying to solve. One popular approach is to change the representation of your reference from a pointer to an index: let mut owned = vec![1, 2]; let ix = 0; let iy = 1; Now, in order to dereference your reference, you need to do `&mut owned[ix]` instead of simply `{STAR}x`. There are various trade offs to this choice of course, including the fact that `&mut owned[ix]` will likely be bounds checked. (Bounds checking could be turned off by using `unsafe { owned.get_unchecked_mut(ix) }`.) But there are more solutions to this problem! Here's another: use std::cell::Cell; let owned = vec![Cell::new(1), Cell::new(2)]; let x = &owned[0]; let y = &owned[1]; x.set(10); y.set(20); println!("{:?}", owned); This uses a concept known as "interior mutability" to permit mutating through a borrowed reference. `Cell` only works for `Copy` types ("plain old data"), whereas you'd need to use `RefCell` for non-copy types like `Vec<T>` or `String`. Finally, we can come full circle and use the `split_at_mut` API on slices to get two mutable views into the same slice. This is a good example of something that is ultimately implemented using `unsafe`, but exposes a `safe` interface: let mut owned = vec![1, 2]; { let (xs, ys) = owned.split_at_mut(1); let x = &mut xs[0]; let y = &mut ys[0]; *x = 10; *y = 20; // The added scope is used so that the mutable // borrow of `owned` is done before borrowing it // again to print its contents below. } println!("{:?}", owned); There are various trade offs to each of these approaches and which one you use depends on the problem you're trying to solve. Of course, the criticism of "I just want to use mutable pointers dammit" is valid, because Rust will, for the most part, force you to think of another way of solving your problem (unless you're OK using `unsafe` and raw pointers). We occasionally pay a price for it when the borrow checker isn't quite smart enough. In the example I've shown in this comment, it's obvious what the borrow checker should do, but in a real program, it's rarely so simple.
- radarsat1 10y ago> That this fails might come as a surprise to a lot of folks because it's easy for a human to see that it's perfectly safe. So what's still unclear to me is, is this failing in the borrow checker "by design", or is it something that will be "improved"? Also, _why_ does it fail in the borrow checker? Is it correct that it fails, or is it a deficiency of the current borrow checker?
- gcp 10y agoIt's a limitation of the current borrow checker.
- Diggsey 10y agoIt would be relatively easy to overcome this limitation, but it would involve making slices "special" to the compiler, which also means another special rule for the programmer to use. It would be preferable if you could make your own slice-like types which supported indexing, but to do that you need a mechanism to tell the compiler that your `IndexMut` implementation returns disjoint references for different indices, and that requires a mechanism for the compiler to determine whether two indices are the same (in case your indices are not integers).
- burntsushi 10y agoIf "correct" corresponds to "if and only if safe," then no, it's not correct because the code I posted in my previous comment is safe, but the borrow checker rejects it. I only posted samples to demonstrate working with the borrow checker. Think about this code: let mut owned = vec![1, 2]; let x = &mut owned[i]; let y = &mut owned[j]; What are the values of `i` and `j`? If they are equivalent, then this code is unsafe, because it would permit mutable aliases. Therefore, the borrow checker would need to prove something about what values of `i` and `j` are legal at this particular point in the program. (Errmmm, dependent types anyone?) With that said, if you do have code like my original sample (where the indices are constants and trivially not equal), then perhaps it would be reasonable to say that is a deficiency of the borrow checker as it currently exists that could be feasibly fixed. It's not clear what the impact of fixing that deficiency would be. For example, I don't think it would have made the OP's life any better.
- CyberDildonics 10y agoshared mutable values are also called globals
- junke 10y agoOnly when they are globals, though.