Live data from Hacker News

The struggle with Rust

ayende.com

11–20 of 301 posts

Re: The struggle with Rust

#11
When 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 work, I have learned not to try and simply replicate Python patterns in OCaml, and as a result I use OCaml to write code quickly and without any headaches; and not only is the type checker no longer an obstacle, it's now a valuable assistant.

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.

Re: The struggle with Rust

#12

These 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

#13
post #8
post #6

Yes, it is hard to use mutable shared values Rust. This is exactly the point.

So how do you avoid it?

Well, 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).

Re: The struggle with Rust

#14

These 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.

That's why I'm really looking forward to the point in time where "non-lexical borrows in Rust stable" and "clippy installable via rustup" converge.

Re: The struggle with Rust

#15

When 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?

Re: The struggle with Rust

#16
post #12

These 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.

Not sure if your comment was intended dismissively/jokingly, but I think also somewhat insightful. I find myself much less often really thinking about how to do something these days; if it seems remotely standard or potentially troublesome I google it. As a result, I spend less time thinking "algorithmically" about low-level stuff and probably lose something. But I also spend more time thinking (perhaps algorithmically) about the business domain, and am ultimately more productive.

Re: The struggle with Rust

#17

When 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 occupies a slightly different niche to Rust as it has a GC. If you can live with that, it's probably going to be more productive than Rust for you.

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

#18
post #5

These 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…

Agreed. Rust is different from other languages in that it is HARD to learn. It took me 3 projects and thousands of lines of code before I finally felt like I'm okay at Rust. Each project involved tons of compiler fighting that I now know how to avoid or solve.

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

#19
I spent the last week working with Rust and learning from the Rustaceans. It's been such a great experience. I am really glad that I dedicated some time to it. In a short amount of time, I've already dramatically improved some computationally expensive python code by more then 10x, largely with the help of the community. Further, Rust is already making me a better programmer. If you are skeptical, I challenge you to dedicate yourself completely to a solid week crash course of Rust and experience this for yourself. If you struggle, don't give up and blog about why you hate Rust. Join #rust-beginners on irc.mozilla.org and ask for help.

Re: The struggle with Rust

#20
post #8
post #6

Yes, it is hard to use mutable shared values Rust. This is exactly the point.

So how do you avoid it?

There'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` 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.
Post reply on HN