I believe that we are seeing the same kind of phenomenon in Rust: we are trying to replicate C or C++ code directly in Rust, and we get frustrated when our efforts are foiled by the borrow checker. It's going to take some time and some efforts before we learn and internalize how borrowing works in Rust and how to change the way we write programs so that it is no longer an obstacle. The Rust team looks very receptive to comments about taking away some pain points (e.g., non-lexical lifetimes), but we also need to accept that, for a little while, we are going to feel like we did when we were new programmers and were trying to make sense of these new constraints.
The struggle with Rust
11–20 of 301 posts
Re: The struggle with Rust
#12These kinds of articles are always hostages to fortune. Because I guarantee somebody, any second now, will demonstrate that what the author wanted to do was trivial, and if he'd spent a bit less time trying to start flamewars on Hacker News he'd have discovered it. Not me, by the way, I don't know rust. But I reckon it'll be about three comments down.
Re: The struggle with Rust
#13Yes, it is hard to use mutable shared values Rust. This is exactly the point.
So how do you avoid it?
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).
Re: The struggle with Rust
#14These kinds of articles are always hostages to fortune. Because I guarantee somebody, any second now, will demonstrate that what the author wanted to do was trivial, and if he'd spent a bit less time trying to start flamewars on Hacker News he'd have discovered it. Not me, by the way, I don't know rust. But I reckon it'll be about three comments down.
Re: The struggle with Rust
#15When I first learned OCaml (knowing only Python), I swore a lot at the type checker for refusing to compile code that I knew would work at run-time (e.g., a variable having multiple types, but on strictly disjoint execution paths). It seemed insane to me that people would want to subject themselves to this kind of bondage and discipline. And yet, many years later now, I have learned and internalized how type systems…
Re: The struggle with Rust
#16These kinds of articles are always hostages to fortune. Because I guarantee somebody, any second now, will demonstrate that what the author wanted to do was trivial, and if he'd spent a bit less time trying to start flamewars on Hacker News he'd have discovered it. Not me, by the way, I don't know rust. But I reckon it'll be about three comments down.
If this were a C++ problem the author wouldn't have blogged because the answer would've been on StackOverflow.
Re: The struggle with Rust
#17When I first learned OCaml (knowing only Python), I swore a lot at the type checker for refusing to compile code that I knew would work at run-time (e.g., a variable having multiple types, but on strictly disjoint execution paths). It seemed insane to me that people would want to subject themselves to this kind of bondage and discipline. And yet, many years later now, I have learned and internalized how type systems…
Not related to the topic but how do you find ocaml as compared to python or rust? Do you think its worth learning today as opposed to rust for high level general purpose programming?
It's definitely worth playing around with a language from that family. OCaml is maybe slightly more practical than Haskell and it has some nice resources available - in particular Real World OCaml looks really good: https://realworldocaml.org/
Re: The struggle with Rust
#18These sorts of articles are starting to pop up a bit more frequently, presumably because Rust is starting to get a bit of traction. The theme is "I know $LOW_LEVEL_LANGUAGE therefore I should be able to program Rust. I spent a few days and couldn't. I don't like Rust." Unfortunately, things aren't that simple and Rust really is different from other languages. It takes months of steady investment (and yes, frustration…
The problem is that instead of a general technique for solving borrow checker issues, every issue has a different specific technique. Hash map matches (like the article) require the entry API. Multiple mutable pointers into a buffer require either using a typed Vec or Vec#split_off. Cyclic data structures require an Arena or Rc>. String casting issues sometimes require .map(|x| &x). Global state requires lazy_static. Nice error handling requires error_chain. When you run into problems with "Send" you need a Mutex. The list goes on...
It's like pure lazy functional programming in Haskell in that unlike other languages you can't just pick it up based on previous knowledge and patterns. You need an entirely new toolbox of pure algorithms and data structures, or in this case borrow-safe algorithms and data structures.
Re: The struggle with Rust
#19Re: The struggle with Rust
#20Yes, it is hard to use mutable shared values Rust. This is exactly the point.
So how do you avoid it?
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` 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.