Live data from Hacker News

The struggle with Rust

ayende.com

141–150 of 301 posts

Re: The struggle with Rust

#141
post #71

Earlier quoted context omitted.

To me it seems like Rust has an additional wrinkle with safe and unsafe code. A new Rust user needs to figure out both "How do I do this" and "Is this possible in safe code". The limits of safe code seems to be something people are figuring out at all levels. Rust projects seem to start out with a relatively high amount of unsafe code. Then as the project matures the unsafe sections are reduced or eliminated.

I think whether you feel you need unsafe when you first come to Rust depends on the angle from which you're coming. Coming from C/C++, I know that there are structs in memory and can visualize what they look like - surely I can just go and tweak them? (No I can't because either it wasn't safe to do that anyway, or I need to explain to the Rust compiler why it is safe, and that can be quite difficult.) Coming from som…

>Either way, it feels like part of the learning curve is learning the "Rustic" way to do something - in general, you probably shouldn't be reaching for "unsafe" until you know what you're doing! ;)

I tend to agree with this sentiment. The moment I saw that OP was doing pointer arithmetic: I knew they were in for a bad time. It's not that you can't do it -- it's moreso that Rust's raison d'etre is to highlight the flaws inherent in that approach.

I hadn't thought about it, but I think you're right, my background in managed languages is probably why I didn't run into these sorts of issues until after I was already comfortable with Rust. I wasn't trying to make maximally storage-efficient collections right out of the gate, instead I was building application-level stuff on top of `std::collections`.

Re: The struggle with Rust

#142
post #106
post #30

Earlier quoted context omitted.

I'm a big fan of hybrid functionals. I think being able to fall back to somewhat imperative code makes sense on occasion. Some things are better expressed in either paradigm, and that's OK.

You can do that perfectly fine even in a pure language like Haskell.

Or maybe Haskell is also a hybrid? I know about the type theoretic accounting tricks used to stay pure, but when I really need unboxed mutable arrays and several tight loops, I just use the ST monad and write code that is psychologically completely imperative. And it compiles to pretty fast executables too, especially using the LLVM backend.

Re: The struggle with Rust

#144

Earlier quoted context omitted.

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

If "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 unsaf…

When in doubt be extra safe.

Re: The struggle with Rust

#145
post #120

Earlier quoted context omitted.

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

> Cyclic data structures require an Arena or Rc >. Is there a way of creating a cyclic data structure that doesn't involve these types? I ask because I used the latter recently, after struggling to create lifetimes that were acceptable to the compiler. I only found it when I searched for 'cyclic data structures rust' on Google. It would have been much nicer if the compiler could have noticed that I was trying to crea…

You could use id/vec indices, instead of directly pointing to your e.g. sucessors: "struct Node { successors: Vec }". This has the obvious disadvantage that deleting nodes is quite hard though.

Re: The struggle with Rust

#146

Earlier quoted context omitted.

They have that with the "unsafe" blocks but like I said promoting it as something you should use when things don't fit borrow-checker goes against their primary goal of making things safe. I guess there are two camps, one that wants compile time safety guarantees - those people made Rust, and the other wants a C++ replacement with modern tooling and no legacy garbage - those are looking at Rust and getting discourage…

You're almost certainly not looking for Rust if you don't want your language to prevent a vast variety of bugs, and instead want something that allows and encourages you to design systems which make it easy to shoot yourself in the foot (which all C++ codebases I've ever seen have grown). I don't think language developers are interested in making such a language in general, as it's not interesting - you'd just be reh…

    I don't think language developers are interested in making
    such a language in general...
https://inductive.no/jai/

:P

I'd argue go came from a desire to have a language that was completely uninteresting in terms of 'interesting ideas', and really just exists to Get Stuff Done.

I think more people are interested in that sort of thing than you imagine. Clearly.

Re: The struggle with Rust

#147
post #112

Earlier quoted context omitted.

It's not very surprising to me. Paraphrasing, "once I got past the initial hurdle of the borrow checker, programming in Rust became much easier," is frequently uttered in the Rust community. I'm not trying to make a very nuanced claim here. All I'm saying is that there's clearly a lot of people who struggle while learning Rust, but that this is very different than paying an eternal development cost similar to that st…

> programming in Rust became much easier Much easier than what? Writing C? I believe you. I agree with you about maintenance, and I agree that there are two separate issues, but still, extra proof must invariably carry some extra mental burden. There are no free proofs just as there are no free prime factors. This isn't a philosophical issue, but a computational one. But it's also true that the mental burden might of…

I don't disagree. But I find this to be a very different claim than the one I initially protested. To re-iterate: I'm trying to point out that there's a difference between learning costs and eternal development costs. The OP is a data point in support of the former, not the latter.

I'm not going to debate the trade offs of type systems with you. It's been done to death.

Re: The struggle with Rust

#148

Earlier quoted context omitted.

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

> Agreed. Rust is different from other languages in that it is HARD to learn. Don't you think C/C++ are hard to learn too? C++ has a vast surface area and I think it's unlikely that most people who use it understand much of it. When I code in C, I feel like I have to be paranoid because it's so easy to forget a path that doesn't recover allocated resources. But if I'm not paranoid, I just leak the resources and don't…

I think it's easier to get started with C++ because it contains simpler languages. You don't have to start with template metaprogramming, you can be productive with a small subset of C++.

Re: The struggle with Rust

#149
post #112

Earlier quoted context omitted.

> programming in Rust became much easier Much easier than what? Writing C? I believe you. I agree with you about maintenance, and I agree that there are two separate issues, but still, extra proof must invariably carry some extra mental burden. There are no free proofs just as there are no free prime factors. This isn't a philosophical issue, but a computational one. But it's also true that the mental burden might of…

I don't disagree. But I find this to be a very different claim than the one I initially protested. To re-iterate: I'm trying to point out that there's a difference between learning costs and eternal development costs. The OP is a data point in support of the former, not the latter. I'm not going to debate the trade offs of type systems with you. It's been done to death.

I doubt there would be much debate, as we're likely on the same side :)

Re: The struggle with Rust

#150
post #122

Earlier quoted context omitted.

From an implementation standpoint that makes sense. From a usability standpoint it does not. The use case of systems programming usually involves many mutators on a single piece of, non-copiable, data. I can see why the person writing the Rust prover would say "yea this is the best" but I can't see why anyone else would because it's filter cuts out a large portion of completely valid code that is not incorrect but in…

> From a usability standpoint it does not. The use case of systems programming usually involves many mutators on a single piece of, non-copiable, data. I can see why the person writing the Rust prover would say "yea this is the best" but I can't see why anyone else would because it's filter cuts out a large portion of completely valid code that is not incorrect but instead is "scary" or "spooky". I find passing owner…

> The hard part is forming the model. Once you actually know how it's supposed to work, proving it is the easy part. But I mean, how would you even explain to another human that a given algorithm is safe, if not by arguments along the line of "look, this function owns this piece of data and this function owns this piece of data and they're called one after the other"?

One example of how this could work is writing a spinlock. Let's use a made up language for now and adapt that to our need.

   alias lock unsigned int;
   lock() make_lock (
      return 0;
   )

   void(ref l) take_lock (
      while (l != 0); // Assume this is atomic for brevity
      l += 1; 
   )

   void(ref l) give_lock ( 
      l -= 1;
   )

This is a "correctish" non-reentrant lock. This has many vectors to see if this is "correct" from a compiler standpoint. The first is that only the alias "lock" can be passed to my lock even though it is just an "unsigned int". Another mode is that I am doing (lock -= 1). Since this is unsigned if my program ever gets into a state where I am doing (0 - 1) in unsigned arithmatic then I am going to wrap around to UInt.MAX_VALUE which is not how subtraction should behave and it should warn me when this situation arises. If the language allows me I can also do this to help the compiler know that I never want this to happen.

   void(ref l) where (l != 0) give_lock (
      l -= 1;
   )
Now the compiler can see I absolutly do not want this wraparound behavior and it can check for it. Where would this be the case?

   int() thread_safe_fac(ref l, int some_important_data) (
      int f;
      take_lock(l);
      if (some_importand_data) {
         f = some_important_data * thread_safe_fac(l, some_important_data - 1);
      } else {
         f = 1;
         give_lock(l);
      }
      give_lock(l);
      return f;
   )

   void() main (
      lock l = make_lock();
      int some_important_data = 10;

      some_important_data = thread_safe_fac(&l, 10);

      print(some_important_data);
   )
Will this code work? Absolutly not and it's easily checkable. One of my function's invariants is violated. give_lock() will sometimes fall negative. This is a very crude example but I've seen code like this in larger projects where someone modified some code and someone refactored that then someone was contracted to add a feature. It just goes down the shitter. But this is easily provablely wrong.

   * Call thread safe fac
   * grab a lock
   * we theoretically touch some thread safe data
   * We hit a case where we do an if
      * Then run the then case
      * else set a default and free our lock
   * Free our lock
We've free'd twice and we've now broken the underyling data of the `lock` type. We've got an unsigned int that has been wrapped around to an huge number and take_lock(l) will never work again. Lock is a mutable datatype that can be shared between N many threads/fucntions and it can always be checked like this just by telling the compiler "hey I never want to let the internal int fall into a state where it is subtracting 1 from 0".

This can also be extended to other things

   void test(int *num) { *num *= 10; }
   int some_complex_function(int *num) {... free(num) ... }

   int main() {
     int *some_cool_data = (int*) malloc(sizeof(int);
     *some_cool_data = 5;

     while (true) {
       ...
       test(some_cool_data);
       ...
       some_complex_function(some_cool_data);
       ...
     }
   }
We've come to a place in the code where we have a possible path of execution (regardless of the depth in if statments or gotos or other control flow) where we will eventually come to the point where we use a bit of data after it's free'd. This code will be correct if you do something like.

   // We now make sure the compiler knows that we only want to accept real references to initialize ptr
   int some_complex_function(int **num) where (num && *num) {... free(*num); *num = NULL; ... }

   // We've removed the path through the program that allows the data to be used after free.
   while (some_cool_data) {
      ...
      some_complex_function(&some_cool_data);
      ...
   }
Again another bad example but there is a lot of perfectly valid, and very clean code that is against the borrow checker but is still completely correct.
Post reply on HN