Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

No, GP can just not use Rust, they don't have to use GC languages to have something that makes sense and doesn't force you to always have a debate with the compiler about even simple things.

If they used Odin (or Zig) they could've looped through that dynamic array no problem, in fact:

    package example
    
    import "core:fmt"
    
    main :: proc() {
        xs: [dynamic]int
        append(&xs, 1, 2)

        for x in xs {
            fmt.println(x)
        }
        
        fmt.println(len(xs))
    }
It is ridiculous that Rust complains even about the simple for loop and to say that this somehow comes down to "Well, everyone would do it this way if they cared about memory safety" is just not really true or valuable input, it sounds like what someone would say if their only systems programming experience came from Rust and they post-rationalized everything they've seen in Rust as being how you have to do it.

My tips to people who maybe feel like Rust seems a bit overwrought:

Look for something else, check out Odin or Zig, they've got tons of ways of dealing with memory that simply sidestep everything that Rust is about (because inherently Rust and everything that uses RAII has a broken model of how resources should be managed).

I learned Odin just by reading its Overview page (https://odin-lang.org/docs/overview/) and trying stuff out (nowadays there are also good videos about Odin on YouTube), then found myself productively writing code after a weekend. Now I create 3D engines using just Odin (and we in fact use only a subset of what is on that Overview page). Things can be simple, straight forward and more about the thing you're solving than the language you're using.



I dunno; I've never tried Zig before, and it wasn't hard to check whether this kind of bug was easy to have:

  const std = @import("std");
  
  pub fn main() !void {
      var gpa: std.heap.GeneralPurposeAllocator(.{})=.{};
      const alloc=gpa.allocator();
  
      var list = try std.ArrayList(u8).initCapacity(alloc, 1);
      const a = try list.addOne();
      a.* = 0;
      std.debug.print("a={}\n", .{a.*});
      const b = try list.addOne();
      b.* = 0;    
      std.debug.print("a={}\n", .{a.*});
      std.debug.print("b={}\n", .{b.*});
  }


  a=0
  Segmentation fault at address 0x7f9f7b240000


I think it is important to note that in 59nadir's example, the reason Rust gives an error and Odin doesn't is not memory safety. Rust uses move semantics by default in a loop while Odin appears to use copy semantics by default. I don't really know Odin, but it seems like it is a language that doesn't have RAII. In which case, copy semantics are fine for Odin, but in Rust they could result in a lot of extra allocations if your vector was holding RAII heap allocating objects. Obviously that means you would need to be careful about how to use pointers in Odin, but the choice of moving or copying by default for a loop has nothing to do with this. For reference:

Odin (from documentation):

  for x in some_array { // copy semantics
  for &x in some_array { // reference semantics
  // no move semantics? (could be wrong on this)
Rust:

  for x in vec.iter_ref().copied() { // bytewise copy semantics (only for POD types)
  for x in vec.iter_ref().cloned() { // RAII copy semantics
  for x in &vec { // reference semantics
  for x in vec { // move semantics
C++:

  for (auto x : vec) { // copy semantics
  for (auto &x : vec) { // reference semantics
  for (auto &&x : vec) { // move semantics


And why do you think that bug is relevant in the case of a loop that prints the elements of a container? We can all see and verify at a glance that the code is valid, it's just not provably valid by the Rust compiler.

I feel like these posts trying to show possible memory issues with re-allocated dynamic arrays are missing the point: There is no code changing the underlying array, there is no risk of any kind of use-after-free error. This is exactly the kind of case where all of this jumping through hoops shouldn't be needed.


> There is no code changing the underlying array, there is no risk of any kind of use-after-free error.

There is none of this code, until there is.


Ok, so we've established that the loop can be verified as not changing the container in any way, what makes you believe this shouldn't be obvious to the Rust compiler?

When code that modifies the container is added, it should be understood and then correctly errored about, I don't get why this is such a crazy concept to people.

The point here is that you pay the cost for an error that can't happen. It's just a micro example of a much more general issue that boils down to:

The Rust compiler does a lot to find and mitigate bugs, it's amazing, but it also rejects completely valid programs and solutions because it simply isn't good enough (and it's a difficult enough problem where I'm prepared to say it will never be good enough). You can either pay that cost constantly and for certain problems be dealing with it (a lot) for no gain whatsoever (because the bugs it was trying to prevent weren't actual issues or are in fact imperatives because the thing you're doing requires them) or you can choose not to.

I don't think it's particularly useful to make excuses for the compiler not understanding very basic things in simple examples and indirectly argue that it would be too complicated to see what the loop is doing and act accordingly. Rust already signed up for a very complicated compiler that does all kinds of crazy things in order to mitigate bugs; this type of introspection would increase the accuracy of it a lot.


> You can either pay that cost constantly and for certain problems be dealing with it (a lot) for no gain whatsoever (because the bugs it was trying to prevent weren't actual issues or are in fact imperatives because the thing you're doing requires them) or you can choose not to.

Alternatively, you can use Rust so much these limitations become second nature, and thus don't make them in the first place.

> I don't think it's particularly useful to make excuses for the compiler not understanding very basic things in simple examples and indirectly argue that it would be too complicated to see what the loop is doing and act accordingly.

Great idea, until it stops working. It runs into the paraphrased quote: "Any sufficiently complicated borrow checker is indistinguishable from Dark Magic".

First you say, well, the compiler should be sufficiently smart to figure out case A1 should work, then you add that, but then arises another case A2 that the compiler is sufficiently smart to figure out and so on.

However, you add a bunch of these "sufficiently smart" borrow rules, and you'll end up with a mess. A1 and A2 don't work if A432 is applied, but do work if A49324 is given if the A4 and A2 are satisfied.

The harder the borrow checker is to understand, the more difficult it is to construct a mental model that's useful.

In summary: while I'm not against improving the borrow checker, but the problem is that it needs to be balanced with the opportunity cost of understanding how it approximately works.


> Ok, so we've established that the loop can be verified as not changing the container in any way, what makes you believe this shouldn't be obvious to the Rust compiler?

I would be quite happy for the Rust compiler to be able to perform more powerful analysis and make writing code easier. What I object to, and I think that quite small Zig code snippet highlights, is that dealing with those shortcomings

> for no gain whatsoever

is also plainly wrong.


I make custom 3D engines and I can tell you that it would not be a net benefit for us to use Rust. That's why I added "for certain problems" as a qualifier; there are use cases where Rust would be a net negative.

There are also plenty of use cases where Rust is actually useful and provides guarantees about things that you want guarantees about.


For anyone curious about Odin and graphics, it seems to work really well:

https://gist.github.com/NotKyon/6dbd5e4234bce967f7350457c1e9...

https://www.youtube.com/watch?v=gp_ECHhEDiA


And how should resources be managed?


In bulk, i.e. not one-by-one as is implied and most used with RAII. RAII works best for a one-by-one use case and in well designed, performant systems the one-by-one use case is either irrelevant, rare or an anti-pattern.


Why does RAII work best in a one-by-one use case? You can have an Arena and it can be managed by RAII. That doesn't mean you're freeing each piece of memory one at a time.


if you want bulk, you can use arrays, vecs, arenas, etc.


Rust, in many ways, is a terrible first systems programming language.

To program a system is to engage with how the real devices of a computer work, and very little of their operation is exposed via Rust or even can be exposed. The space of all possible valid/safe Rust programs is tiny compare to the space of all useful machine behaviours.

The world of "safe Rust" is a very distorted image of the real machine.


> Rust, in many ways, is a terrible first systems programming language.

Contrariwise, Rust in, in many way, an awesome first systems programming language. Because it tells you and forces you to consider all the issues upfront.

For instance in 59nadir's example, what if the vector is a vector of heap-allocated objects, and the loop frees them? In Rust this makes essentially no difference, because at iteration you tell the compiler whether the vector is borrowed or moved and the rest of the lifecycle falls out of that regardless of what's in the vector: with a borrowing iteration, you simply could not free the contents. The vector generally works and is used the same whether its contents are copiable or not.


A lot of idiomatic systems code is intrinsically memory unsafe. The hardware owns direct references to objects in your address space and completely disregards the ownership semantics of your programming language. It is the same reason immediately destroying moved-from objects can be problematic: it isn’t sufficient to statically verify that the code no longer references that memory. Hardware can and sometimes does hold references to moved-from objects such that deferred destruction is required for correctness.

How is someone supposed to learn idiomatic systems programming in a language that struggles to express basic elements of systems programming? Having no GC is necessary but not sufficient to be a usable systems language but it feels like some in the Rust community are tacitly defining it that way. Being a systems programmer means being comfortable with handling ambiguous object ownership and lifetimes. Some performance and scalability engineering essentially requires this, regardless of the language you use.


None of these "issues" are systems issues, they're memory safety issues. If you think systems programming is about memory saftey, then you're demonstrating the problem.

Eg., some drivers cannot be memory safe, because memory is arranged outside of the driver to be picked up "at the right time, in the right place" and so on.

Statically-provable memory saftey is, ironically, quite a bad property to have for a systems programming language, as it prevents actually controlling the devices of the machine. This is, of course, why rust has "unsafe" and why anything actually systems-level is going to have a fair amount of it.

The operation of machine devices isnt memory safe -- memory saftey is a static property of a program's source code, that prevents describing the full behaviour of devices correctly.


Water is wet.

Yes, touching hardware directly often requires memory unsafety. Rust allows that, but encourages you to come up with an abstraction that can be used safely and thereby minimize the amount of surface area which has to do unsafe things. You still have to manually assert / verify the correctness of that wrapper, obviously.

> This is, of course, why rust has "unsafe" and why anything actually systems-level is going to have a fair amount of it.

There are entire kernels written in Rust with less than 10% unsafe code. The standard library is less than 3% unsafe, last I checked. People overestimate how much "unsafe" is actually required and therefore they underestimate how much value Rust provides. Minimizing the amount of code doing unsafe things is good practice no matter what programming language you use, Rust just pushes hard in that direction.


> For instance in 59nadir's example, what if the vector is a vector of heap-allocated objects, and the loop frees them?

But the loop doesn't free them. This is trivial for us to see and honestly shouldn't be difficult for Rust to figure out either. Once you've adopted overwrought tools they should be designed to handle these types of issues, otherwise you're just shuffling an esoteric burden onto the user in a shape that doesn't match the code that was written.

With less complicated languages we take on the more general burden of making sure things make sense (pinky-promise, etc.) and that is one that we've signed up for, so we take care in the places that have actually been identified, but they need to be found manually; that's the tradeoff. The argument I'm making is that Rust really ought to be smarter about this, there is no real reason it shouldn't be able to understand what the loop does and treat the iteration portion accordingly, but it's difficult to make overcomplicated things because they are exactly that.

I doubt that most Rust users feel this lack of basic introspection as to what is happening in the loop makes sense once you actually ask them, and I'd bet money most of them feel that Rust ought to understand the loop (though in reading these posts I realize that there are actual humans that don't seem to understand the issue as well, when it's as simple as just reading the code in front of them and actually taking into account what it does).


> But the loop doesn't free them.

What if it did free them in a function you don't directly control?


> forces you to consider all the issues upfront.

Ever wonder why we do not train pilots in 737s as their first planes? Plenty of complex issues do NOT, in fact, need to be considered upfront.


YMMV, naturally, but I've found that some embedded devices have really excellent hardware abstraction layers in Rust that wrap the majority of the device's functionality in an effectively zero-overhead layer. Timers? GPIO? Serial protocols? Interrupts? It's all there.

- https://docs.rs/atsamd-hal/

- https://docs.rs/rp2040-hal/




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: