←back to thread

Four Years of Jai (2024)

(smarimccarthy.is)
166 points xixixao | 4 comments | | HN request time: 0.803s | source
Show context
pcwalton ◴[] No.43726315[source]
> I’d be much more excited about that promise [memory safety in Rust] if the compiler provided that safety, rather than asking the programmer to do an extraordinary amount of extra work to conform to syntactically enforced safety rules. Put the complexity in the compiler, dudes.

That exists; it's called garbage collection.

If you don't want the performance characteristics of garbage collection, something has to give. Either you sacrifice memory safety or you accept a more restrictive paradigm than GC'd languages give you. For some reason, programming language enthusiasts think that if you think really hard, every issue has some solution out there without any drawbacks at all just waiting to be found. But in fact, creating a system that has zero runtime overhead and unlimited aliasing with a mutable heap is as impossible as finding two even numbers whose sum is odd.

replies(4): >>43726355 #>>43726431 #>>43727184 #>>43731326 #
mjburgess ◴[] No.43726431[source]
Well, 1) the temporary allocator strategy; and 2) `defer` kinda go against the spirit of this observation.

With (1) you get the benefits of GC with, in many cases, a single line of code. This handles a lot of use cases. Of those it doesn't, `defer` is that "other single line".

I think the issue being raised is the "convenience payoff for the syntax/semantics burden". The payoff for temp-alloc and defer is enormous: you make the memory management explicit so you can easily see-and-reason-about the code; and it's a trivial amount of code.

There feels something deeply wrong with RAII-style langauges.. you're having the burden to reason about implicit behaviour, all the while this behaviour saves you nothing. It's the worst of both worlds: hiddenness and burdensomeness.

replies(2): >>43726458 #>>43729593 #
hmry ◴[] No.43726458[source]
Neither of those gives memory safety, which is what the parent comment is about. If you release the temporary allocator while a pointer to some data is live, you get use after free. If you defer freeing a resource, and a pointer to the resource lives on after the scope exit, you get use after free.
replies(2): >>43726531 #>>43726581 #
francasso ◴[] No.43726531[source]
While technically true, it still simplifies memory management a lot. The tradeoff in fact is good enough that I would pick that over a borrowchecker.
replies(1): >>43726637 #
junon ◴[] No.43726637[source]
I don't understand this take at all. The borrow checker is automatic and works across all variables. Defer et al requires you remember to use it, and use it correctly. It takes more effort to use defer correctly whereas Rust's borrow checker works for you without needing to do much extra at all! What am I missing?
replies(3): >>43726754 #>>43726792 #>>43729431 #
francasso ◴[] No.43726792[source]
If your use case can be split into phases you can just allocate memory from an arena, copy out whatever needs to survive the phase at the end and free all the memory at once. That takes care of 90%+ of all allocations I ever need to do in my work.

For the rest you need more granular manual memory management, and defer is just a convenience in that case compared to C.

I can have graphs with pointers all over the place during the phase, I don't have to explain anything to a borrow checker, and it's safe as long as you are careful at the phase boundaries.

Note that I almost never have things that need to survive a phase boundary, so in practice the borrow checker is just a nuissance in my work.

There other use cases where this doesn't apply, so I'm not "anti borrow checker", but it's a tool, and I don't need it most of the time.

replies(1): >>43729779 #
1. Rusky ◴[] No.43729779[source]
You can explain this sort of pattern to the borrow checker quite trivially: slap a single `'arena` lifetime on all the references that point to something in that arena. This pattern is used all over the place, including rustc itself.

(To be clear I agree that this is an easy pattern to write correctly without a borrow checker as well. It's just not a good example of something that's any harder to do in Rust, either.)

replies(1): >>43730454 #
2. francasso ◴[] No.43730454[source]
I remember having multiple issues doing this in rust, but can't recall the details. Are you sure I would just be able to have whatever refs I want and use them without the borrow checker complaining about things that are actually perfectly safe? I don't remember that being the case.

Edit: reading wavemode comment above "Namely, in Rust it is undefined behavior for multiple mutable references to the same data to exist, ever. And it is also not enough for your program to not create multiple mut - the compiler also has to be able to prove that it can't." that I think was at least one of the problems I had.

replies(2): >>43730563 #>>43731673 #
3. steveklabnik ◴[] No.43730563[source]
The main issue with using arenas in Rust right now is that the standard library collections use the still-unstable allocator API, so you cannot use those with them. However, this is a systems language, so you can use whatever you want for your own data structures.

> reading wavemode comment above

This is true for `&mut T` but that isn't directly related to arenas. Furthermore, you can have multiple mutable aliased references, but you need to not use `&mut T` while doing so: you can take advantage of some form of internal mutability and use `&T`, for example. What is needed depends on the circumstances.

4. Rusky ◴[] No.43731673[source]
wavemode's comment only applies to `&mut T`. You do not have to use `&mut T` to form the reference graph in your arena, which indeed would be unlikely to work out.