News for hackers. Stuff that matters.
Re:Writing Pythonic Rust by forrestthewoods on Monday May 24, 2021 @04:28PM (#27269316)
> It only means that the data model doesn't agree with Rust's rules for modeling data (which exist to ensure memory safety in the absence of a GC).

This doesn’t seem correct at all?

GC merely solves freeing memory when it is no longer needed. But it does not solve parallel access.

One of the beauties of Rust is that you can write highly parallel code and if it compiles it works. Meanwhile Python languishes behind the GIL. GC does not even attempt to solve multithreaded memory access.

I’m happy to be corrected if I’m wrong or missing something here.

Re:Writing Pythonic Rust by throwaway894345 on Monday May 24, 2021 @06:35PM (#27270494)
You're right on the local point that Rust's ownership rules guarantee correctness in the case of parallel access while GCs do not; however, the broader point is that Rust's ownership rules also reject many programs which would be correct with GC.

For example:

    fn foo<'a>() -> &'a [u8] {
        let v = vec![0, 1, 2];

        // returns a value referencing data owned by the current function
        v.as_slice()
    }
vs Go's:

    func foo() []uint8 {
        // subject to escape analysis, but code like this would likely return
        // a fat pointer into the heap--no complaints because there's a GC.
        return []uint8{0, 1, 2}
    }
> One of the beauties of Rust is that you can write highly parallel code and if it compiles it works. Meanwhile Python languishes behind the GIL. GC does not even attempt to solve multithreaded memory access.

Python's GIL is unrelated to GC, but yes, Rust's borrow checker guarantees correct parallel access of memory. But I think this benefit is overblown for a couple reasons:

1. contrary to popular opinion, if you've learned how to write parallel programs, it's not tremendously difficult to write them correctly without a borrow checker. In my experience, whatever time I've saved debugging pernicious data races is lost by the upfront cost of pacifying the borrow checker. Maybe this wouldn't hold for people who aren't experienced with writing parallel code (but I imagine such people would have a harder time grokking the borrow checker as well).

2. most data races in my experience aren't single threads on a host accessing a piece of shared memory, but rather many threads on many hosts accessing some network resource (e.g., an S3 object). The borrow checker doesn't help here at all, but you still have to "pay the borrow checker tax".

Again, this isn't a tirade against the borrow checker, but an insistence that tradeoffs exist and it's not just a "you're just doing it wrong" sort of thing.

Re:Writing Pythonic Rust by forrestthewoods on Monday May 24, 2021 @07:04PM (#27270721)
Ehhh I’d have to hard disagree with #1. But we’ll likely just have to agree to disagree.

Maybe I’m just a bad enough programmer I write parallel bugs. But C++ certainly doesn’t make it easy to write correct code in any way.

I personally think it’s pretty darn difficult to ensure correctness in a large program. Especially when multiple programmers are involved. And especially when you are adding features to an existing system.

However I will also admit that I haven’t written a large Rust program so I can’t claim to have run into all of its warts.

There’s no silver bullets in life. I work primarily in video games. GC is the bane of existence and is something that provides seriously negative value.

We definitely agree it’s all a trade off. GC provides some value and some costs. Borrow checkers provide some value and some cost.

> It only means that the data model doesn't agree with Rust's rules for modeling data (which exist to ensure memory safety in the absence of a GC).

I think my initial response was that Rust’s model exist for more than just that single reason. Whether those reasons or trade offs are useful depend on the program in question.

In my work I never want a GC, but damn would I love a borrow checker.

Re:Writing Pythonic Rust by jandrewrogers on Monday May 24, 2021 @11:35PM (#27272341)
C++ provides the tools to build robust parallelism that is also optimal, with the giant caveat that implementing it is left to the programmer (and good libraries are not abundant). Rust offers built-in correctness but not optimality, and C++ offers optimality but no built-in correctness. Many massively parallel codes and virtually all massively concurrent codes necessarily lean on latency hiding mechanics for concurrency and safety. Ownership-based safety can’t be determined at compile-time in these cases, but you can prove in many cases that safety can be dynamically resolved by the scheduler at runtime regardless of how many mutable references exist. This has a lot of similarities to agreement-free consistency in HPC, where no part of the system has the correct state of the system but you can prove that the “system” will always converge on a consistent computational result (another nice set of mechanics from the HPC world).

The problem with ownership-based safety for massive parallelism is that the mechanics of agreeing on and determining ownership don’t scale and often can’t be determined at compile-time. Some other safety mechanics don’t have these limitations. C++ doesn’t have them built-in but you can implement them.

Re:Writing Pythonic Rust by throwaway894345 on Monday May 24, 2021 @10:29PM (#27272033)
> Maybe I’m just a bad enough programmer I write parallel bugs. But C++ certainly doesn’t make it easy to write correct code in any way. I personally think it’s pretty darn difficult to ensure correctness in a large program.

IMO the key to writing parallel code is to keep the parallelism confined to a small kernel rather than sprawling throughout your codebase. If you try to bolt on parallelism then you’re going to have a bad time. It needs to be part of your architecture. It’s not easy, but it’s easier than writing parallel code that will pass the borrow checker IMHO. But yes, we may have to agree to disagree.

> We definitely agree it’s all a trade off. GC provides some value and some costs. Borrow checkers provide some value and some cost.

Agreed!

> I work primarily in video games. GC is the bane of existence and is something that provides seriously negative value.

I’m very curious about videogames development. In particular, I get the impression that aversion to GC in videogames comes down largely to experiences with Java back when pauses could be 300ms. I’m very curious if Go’s sub-millisecond GC (and its ability to minimize allocations, etc) would be amenable to videogame development. Thoughts?

> I think my initial response was that Rust’s model exist for more than just that single reason. Whether those reasons or trade offs are useful depend on the program in question.

Heartily agree.

> In my work I never want a GC, but damn would I love a borrow checker.

In my line of work, I like the idea of using Rust but realistically the economic sweet spot is something like “Go with sum types” or “Rust-lite”.

Re:Writing Pythonic Rust by forrestthewoods on Monday May 24, 2021 @11:37PM (#27272347)
> I’m very curious about videogames development. In particular, I get the impression that aversion to GC in videogames comes down largely to experiences with Java back when pauses could be 300ms. I’m very curious if Go’s sub-millisecond GC (and its ability to minimize allocations, etc) would be amenable to videogame development. Thoughts?

It really just comes down to average/worst case time.

60Hz - 16ms 90Hz - 11ms 120Hz - 8ms 144Hz - 7ms 240Hz - 4ms

Modern games are expected to run anywhere from 60 to 240 frames per second. 60 is the new baseline. VR runs anywhere from 72 to 120. Gaming monitors regularly hit 144Hz. And esports goes as high as 240 and even 360.

In Unity C# GC can take tens of milliseconds. This is, uh, obviously very bad. High tier unity games spend a LOT of time avoiding all allocs. This is not fun in a GC language. Most indie games just hitch and its pretty obvious. I’m not sure if Unity’s incremental GC has graduated from experimental mode.

If a GC had a worst case time of less than a millisecond that’d be fine. That’s actually a pretty big chunk of a 7ms frame, but hey probably worth it. If it’s usually 250us but once a minute spikes to 3ms that’ll cause a frame to miss. If once every 5 minutes it’s a 50ms GC that’s a huge hitch. For a single player game it’s sloppy. For a competitive multiplayer game it’s catastrophic.

Unreal Engine actually has a custom garbage collector. But it’s only for certain days types and not all memory. That’s a nice compromise. Games in particular are good at knowing if the lifetime of an allocation is short, per frame, long-term, etc.

Re:Writing Pythonic Rust by colejohnson66 on Monday May 24, 2021 @10:45PM (#27272108)
> I’m very curious about videogames development. In particular, I get the impression that aversion to GC in videogames comes down largely to experiences with Java back when pauses could be 300ms. I’m very curious if Go’s sub-millisecond GC (and its ability to minimize allocations, etc) would be amenable to videogame development. Thoughts?

Well, Minecraft is written in Java, and it runs fine from what I’ve heard. In .NET land, there was a short lived toolkit for C# called XNA - Terraria is (was?) written in it. Both Java and C# are garbage collected.

I haven’t looked at Unity too deeply, but isn’t Unity (and the games made in it) built in C#?

Re:Writing Pythonic Rust by xfer on Tuesday May 25, 2021 @12:03AM (#27272476)
Game programmers mostly want tight control of object layout and lifecycle. GC doesn't matter much when use ECS all over your codebase. As long as they can run the GC when they want and can layout the objects flat without needing pointer indirection it would be very suitable.

I think C# is popular because it allows the above. When Java has proper value types it might be suitable for writing games.

Re:Writing Pythonic Rust by throwaway894345 on Tuesday May 25, 2021 @10:48AM (#27277513)
> As long as they can run the GC when they want and can layout the objects flat without needing pointer indirection it would be very suitable.

To be clear, the problem isn’t pointer indirection, but rather lots of objects on the heap, right? Pointers should be fine as long as there aren’t many allocations (e.g., pointers into an arena)?

> I think C# is popular because it allows the above. When Java has proper value types it might be suitable for writing games.

Go also has value types, FWIW, and they are a lot more idiomatic than in C# from what I’ve observed.

Re:Writing Pythonic Rust by xfer on Tuesday May 25, 2021 @12:09AM (#27272507)
> In my line of work, I like the idea of using Rust but realistically the economic sweet spot is something like “Go with sum types” or “Rust-lite”.

AKA Ocaml.

Re:Writing Pythonic Rust by throwaway894345 on Tuesday May 25, 2021 @06:48AM (#27275250)
Yeah, in theory, but I’ve never had much luck with OCaml and every time I dig into my problems here it ends with the OCaml fanboys berating me so I’ve pretty much given up on it.
Re:Writing Pythonic Rust by pjmlp on Tuesday May 25, 2021 @02:21AM (#27273403)
Or F#, with the bonus of .NET ecosystem and IDEs.
Re:Writing Pythonic Rust by richardwhiuk on Monday May 24, 2021 @06:47PM (#27270581)
The equivalent to the go is returning the Vec.
Re:Writing Pythonic Rust by throwaway894345 on Monday May 24, 2021 @09:15PM (#27271651)
My example is probably not very good. Let’s say you have a function which implements a callback. The function takes a &str and returns some subslice. Let’s say you want to implement another function which implements that same interface, but returns the string lowercases. In Rust, you have to update the interface, you have to update all implementations, and you have to jump through hoops to avoid unnecessary cloning in the subslice case. In Go or other GC languages, there aren’t “owned” vs “unowned” types, so you don’t have to make any updates to the interface or other implementations. In a sense, GC allows us to abstract over ownership.
Re:Writing Pythonic Rust by layoutIfNeeded on Monday May 24, 2021 @06:54PM (#27270643)
>Rust's ownership rules guarantee correctness in the case of parallel access while GCs do not

That’s a dangerous misconception. The Rust ownership model only guarantees that the program is free of data races. That’s a necessary but not sufficient condition of program correctness.

Re:Writing Pythonic Rust by pjmlp on Tuesday May 25, 2021 @07:47AM (#27275670)
>Rust's ownership rules guarantee correctness in the case of parallel access while GCs do not

Only for threads accessing in-memory data structures, it does nothing for other kinds of data access scenarios either to external resources or via OS IPC mechanisms.

Re:Writing Pythonic Rust by throwaway894345 on Tuesday May 25, 2021 @12:45PM (#27279130)
Agreed, see my #2 above.
Re:Writing Pythonic Rust by verdagon on Monday May 24, 2021 @05:55PM (#27270149)
That's a fair point.

I'd add: some GC'd languages solve this with sending around deeply immutable objects (functional languages mostly), in a way that can be more flexible than the way Rust handles immutable objects.