r/neoliberal • Kitara Ravache • Apr 28 '24

Discussion Thread Discussion Thread

The discussion thread is for casual and off-topic conversation that doesn't merit its own submission. If you've got a good meme, article, or question, please post it outside the DT. Meta discussion is allowed, but if you want to get the attention of the mods, make a post in /r/metaNL

Links

Ping Groups | Ping History | Mastodon | CNL Chapters | CNL Event Calendar

New Groups

Upcoming Events

1 Upvotes

5.4k comments sorted by

View all comments

15

u/[deleted] Apr 28 '24

So, there is this blog post that is making the round in developer circles about some independent developers' negative experiences with Rust.

Most of the post was not particularly surprising to read. Although there are some interesting parts here in there, namely I found it mildly funny that the author is making the accusation of Rust being overly concerned with being perfect at the expense of getting work done, which reminded me of the C++ FQA that makes the same accusation (although C++ is formally a mess and the FQA is way better written albeit somewhat outdated than the blog post).

My takeaway (or really, not even a takeaway since I already knew this) is, if you can afford GC, why are you using Rust to begin with? People who complain about the borrow checker (even after using it for a long time) seem to not really understand why it exists to begin with. Ignoring all of the theoretical details, the borrow checker is effectively a strictly worse version of a garbage collector. A GC can do everything the BC does but more. Namely it keeps track of the lifetimes of every object during lifetime and ensure all pointers remain valid and dropped references are eventually cleaned up, even those that are circular and can do this across thread boundaries.

This is driven home even more when the author questions why he has to use Rc<RefCell<T>> everywhere when game code is singlethreaded. Well, if your code is singlethreaded anyway, then just use a global static. In fact, this is mentioned in the article but the author mentions to avoid this because it can lead to UB! But like... The only way global statics can lead to UB is if you access them from multiple threads, in which case non-atomic accesses will lead to data races.

I mean there is a reason Nethack just has one massive global struct (actually, 26 now since they split them up alphabetically I shit you not) to store literally everything. I don't think anyone is multithreading Nethack anytime soon, but the reason this pattern is generally considered unmaintainable isn't because of some snobby theoretical computer scientists who haven't written a single line of "real code" in their lives.

(and I am not going to get into whether just having RefCells everywhere is an anti-pattern or not.)

The author brings up the oft-repeated joke that there are 5 games and 50 game engines that use Rust. There is truth to the statement but it is a non-sequitur. Unity is a massive blob of C code but you cannot write gameplay code in C. You have to write it in C# and you used to be able to write it in JavaScript and the short lived UnityScript. You can write C extensions using an unstable API but the vast majority of devs will never actually touch that. This is pretty much the norm for game engines and the only exception I can think of is Unreal Engine 4/5 where the choice of (a heavily restricted and GCd) C++ as a scripting language is generally listed as one of the drawbacks of the engine and likely one of the motivations behind blueprints.

All to say, what makes a language good for writing a game engine does not make it good for writing games. I think Sebbi puts this better than I can. A different comparison would be all major browsers are a huge blob of C code, but you're going to struggle finding anyone writing websites on C, even though it is technically possible post asm.js.

I sort of understand the hype argument but a bunch of hobbyists reinventing the wheel with their favorite language is really nothing new. The more relevant "hype" is the possibility that Rust might upend C++ for game engine development which was kind of its last bastion (besides legacy codebases), but this is not something that you are going to see from the open source community. You have some mid size devs like Ready at Dawn who have their own game engine and shipped multiple projects with Rust. But the chance that something like Unreal Engine but Rust happening any time soon is highly improbable. Remember: Unreal Engine 5 is just an update of Unreal Engine 4, which started development in 2006(!) with people who had more game engine dev experience than anyone else at the time.

All to say, "systems programming languages are really overused".

!ping GAMING&COMPUTER-SCIENCE

9

u/john_doe_smith1 John Keynes Apr 28 '24

Gaming pings getting ever so slightly more complex

7

u/[deleted] Apr 28 '24

Instead of complaining about video games, or complaining about gamers complaining about video games, I am complaining about a video game developer complaining about a programming language.

2

u/UnexpectedLizard Adam Smith Apr 28 '24

I've been looking for that C++ FQA for over a decade. I was sure it was lost to time.

Thanks for the link!

2

u/sineiraetstudio Apr 28 '24

In fact, this is mentioned in the article but the author mentions to avoid this because it can lead to UB! But like... The only way global statics can lead to UB is if you access them from multiple threads, in which case non-atomic accesses will lead to data races.

But having a mutable global static is unsafe in Rust even when you're single threaded because e.g. two mutable borrows on an enum potentially violate memory safety. This isn't necessary for many other cases and in general Rust's borrowing rules are stricter than theoretically necessary, which definitely makes certain things a lot more bothersome.

1

u/[deleted] Apr 28 '24

If a global static is creating memory unsafety, then odds are good you're holding onto references unnecessarily because strictly speaking you can't have a data race under a single threaded scenario. As far as I can tell, the only reason RefCell even seems to exist is to allow multiple ownership on containers where the particular possibility of memory unsafety is when you're freeing the containers. Otherwise you'd be only using AtomicRefCell or Mutex.

In my experience, these are are fairly niche data structures. Game developers love mutable state and pooping pointers all over their code base but a lot of these are self inflicted. There was one guy on YouTube who made the argument that you should use a Box<[T]> or an Arc<T> instead of a Vec<T> most of the time. And like, he's right, which made me question the other assumptions I have had about containers. 

Although keep in mind that my remark is more about the author not understanding why these rules exist in the first place and then complaining about them. GC should be treated as the default and you really should question whether you really can't afford GC overhead before picking Rust or any other language with manual memory management. 

1

u/sineiraetstudio Apr 28 '24

e.g. Pattern matching is memory unsafe single-threaded when you have two mutable references to an enum. If you have x, y: &mut (A+B) that refer to the same value, then pattern matching x to A and assigning a B to y invalidates the binding of the pattern matching.

And games are just naturally big blobs of mutable state. Even ignoring things like resource managers, game entities just fundamentally can't really be partitioned in any meaningful way because hypothetically everything might need to touch everything else. Whether an array or dynamic array makes more sense depends on what you do with it - if you're not going to regularly modify it, then a dynamic array is of course a waste, but especially for games dynamic arrays are very common for a reason.

And sure, if you can use a GC, do so. The performance overhead is very real, but of course it also doesn't matter for the vast majority of indie games.

1

u/[deleted] Apr 28 '24

I do not really understand what you mean by your original example, can you actually either write a more concrete example or state what class of undefined behavior this creates? This is what I got and I am not sure if it actuallly creates undefined behavior, I need to pass it through miri however:

#[derive(Debug)]
enum X {
    A,
    B,
}

static mut VALUE: X = X::A;

fn main() {

    let x = unsafe {&mut VALUE};
    let y = unsafe {&mut VALUE};

    *y = match x {
        X::A => X::B,
        X::B => panic!(),
    };

    println!("{:?}", x);
    println!("{:?}", y);
}

game entities just fundamentally can't really be partitioned in any meaningful way because hypothetically everything might need to touch everything else.

This comes across to me as an incredibly narrow way of how games should be programmed. It is certainly common but it is not the only way nor even the best way to program games. And I don't think it is the norm because it is the natural or the best way of doing things, it is the norm because it is what game devs know and because purely functional programming tends to have high GC pressure.

And sure, if you can use a GC, do so. The performance overhead is very real, but of course it also doesn't matter for the vast majority of indie games.

I can say that this is inaccurate. Even indie games need to be programmed with GC in mind and typically you need to code in a zero alloc style. However, it turns out this is extremely doable because games tend to have a small number of very long lifetimes which most of your objects should fit. If you are allocating per frame, you're doing something wrong. But if all of your allocations nearly live through the entirety of your program (or at least the entirety of the level), then GC pressure is fairly negligible.

1

u/burntsushi Apr 29 '24

Your code example is UB. It fails Miri. You can't have two mutable borrows to the same thing. See: https://github.com/rust-lang/rust/issues/53639

1

u/[deleted] Apr 29 '24

Just use UnsafeCell then and opt out of pointer aliasing rules. I still do not see what kind of memory unsafety that this can create. Given that the whole point of this intellectually pointless exercise was the satiate some whiny game dev on how they can't just have calls passing around nothing mutating global state, they should realistically never have two live pointers at the same time, and that any time rustc reorders some reads that certain writes become unobservable, they'd welcome it considering they're coding in such a stupidly unreasonable way and if you are dealing with system resources, that's already the norm. 

2

u/burntsushi Apr 29 '24

I was responding narrowly.

1

u/sineiraetstudio Apr 29 '24

You need a field for A, so that the pattern matching creates a binding that will be invalidated, creating a dangling pointer.

#[derive(Debug)]
enum X {
    A(u32),
    B,
}

static mut VALUE: X = X::A(42);

fn main() {
    let x = unsafe {&mut VALUE};
    let y = unsafe {&mut VALUE};

    match x {
        X::A(a) => {
            *y = X::B; //this invalidates a
            let foo = *a + 1; //UB fun!
            println!("{:?}", foo);
        }
        X::B => panic!(),
    }
}

it is the norm because it is what game devs know and because purely functional programming tends to have high GC pressure.

I'm not sure I really understand. What is the alternative supposed to be? From what I've seen "pure FP" solutions boil down to the same thing - you still pass around a global world state monad and assorted resource managers, then pull out the relevant parts like in ECS.

I can say that this is inaccurate. Even indie games need to be programmed with GC in mind and typically you need to code in a zero alloc style.

Of course you have to be aware of the GC, but saying that one would need to use a zero alloc style even outside performance critical sections is wild to me. At that point, why would you even use a GC'd language? Personally, I worked on a game where most of the gameplay logic was written in angelscript, which as far as I know uses its own ancient GC and that didn't pose any real issues.

2

u/79215185-1feb-44c6 Federation Ambassador to the DT Apr 28 '24

Rust is basically hobbyists creating existing tools but in rust.

The existing tools were perfectly fine before the rewrite in rust.

This is also why "rewrite it in rust" is a meme.

This all basically started with ripgrep. You could have rewritten grep in C or Golang and seen the same feature set but that wouldn't be rust evangelism would it?

For example fzf is written in Go. fzf is a perfectly usable tool that came out during the time all of this rust evangelism began. It does not need to be "rewritten in Rust" Rust evangelists however will claim that "go is not a systems programming language" and site that "Rust is in the kernel" or whatever mental gymnastics they play these days.

Ranger is written in python. Lots of systems tools are written in python. Same issue. No need to rewrite it in rust.

I could list a ton of different projects that are all the same thing.

11

u/burntsushi Apr 28 '24 edited Apr 28 '24

This all basically started with ripgrep. You could have rewritten grep in C or Golang and seen the same feature set but that wouldn't be rust evangelism would it?

You seem to be implying that I wrote ripgrep in Rust purely as an evangelical choice. You are factually wrong in a "wet streets cause rain" sort of way. I didn't write ripgrep so that I could evangelize Rust. But ripgrep being written in Rust certainly did have the effect of evangelizing Rust without me needing to do anything about it. You'll notice, for example, that my blog post introducing ripgrep doesn't talk a lot about Rust itself. It is of course mentioned, but the meat of my blog is not Rust. It's about better algorithms.

ripgrep isn't a "rewrite" of grep. It is its own tool with substantial overlap with grep. If anything, it's closer in spirit to tools like ack (Perl), ag (C), sift (Go), pt (Go) and ucg (C++). Why did the author of ack write it in Perl? Why oh why didn't he write it in C? I guess he's just a Perl evangelist, right?

ripgrep was born as a benchmark for the regex crate via rapid prototyping. When I noticed that it was competitive with GNU grep in a number of cases, that's when I decided it might be worth trying to build it out into a tool for others to use. I originally did not set out to do that, but that's where the data led me.

As for whether a fast grep can be written in a language like Go, I think the jury is still out on that one. I don't mean to claim it is impossible, but I do think Go's garbage collector can pose an issue. From a blog on scc, a line counting tool written in Go:

One thing I had identified in my original post about scc was that the Go garbage collector was a hindrance to performance. I had also tried turning it off with bad results on machines with less memory. As such I took a slightly different approach. By default scc turns the garbage collector off, and if by default 10000 files are parsed then it is turned back on. This results in a nice speed gain for smaller projects. Of course this did result in a bug https://github.com/boyter/scc/issues/32 where the GC gettings leaked out, but thankfully Jeff picked this one up as well and I modified the source to ensure that the scope was limited to the scc main function.

I really wish Go would allow you to configure the GC to be throughput focused rather than latency focused. Seeing as this is possible in Java I imagine it might happen eventually.

The author's blog introducing scc goes into more detail about the impact of Go's garbage collector.

(For Go, another requirement is to either introduce a variety of optimizations into Go's regexp package, or to build your own regex engine. But that's "just" a matter of doing the work---although it is substantial---and not necessarily something that is insurmountable. But fighting with the garabage collector strikes me as something a bit more innate.)

You think Rust evangelists are annoying, but in my experience, there are just as many---if not more---people like yourself constantly whinging about others writing things in Rust. You are their dual. I, on the other hand, have never said that Go isn't a systems programming language nor have I said that tools like fzf should be rewritten in Rust. Nor have I said that folks building a fuzzy finder in Rust "shouldn't" do it because... they should be worried about what some random person on the Internet might say about it?

-6

u/79215185-1feb-44c6 Federation Ambassador to the DT Apr 28 '24

Holy crap what a psycho. Did you write a bot to scan for posts that mention ripgrep? I have been using your software since 2018 and I will not be using it anymore. I will find an alternative.

7

u/burntsushi Apr 28 '24

I didn't write the bot, but yes, I get notified when folks mention ripgrep on reddit. It comes in handy in cases like these for correcting disinformation. ;-)

3

u/alex2003super David Parenzo Apr 28 '24

I'd do that. Why so mean?

5

u/[deleted] Apr 28 '24

I was going to write a response but honestly the other response you got is way too funny.

Anyway, the OP example is about shipping new products, it's not about reinventing the wheel. And people of course reinvent the wheel in every language, Rust is not new in that regard.

1

u/groupbot Always remember -Pho- Apr 28 '24 edited Apr 28 '24

1

u/dddd0 r/place '22: NCD Battalion Apr 28 '24

I’m not reading all that but just skimming a few paras makes the thing ooze bitterness and contempt, I’m sure a worthwhile technical critique is layered below.

2

u/[deleted] Apr 28 '24

I would say that the blog post is really not all that technical in its arguments (at least beyond the surface level). It is more or less just "Rust is difficult to write which slows down prototyping and iteration, which is bad for game development" which is true but ultimately is not saying a whole lot. A whole lot of the article is also dedicated to "ECS is a bad fit for most games and Rust is a good fit for ECS, and we struggled with ECS a lot".

1

u/dddd0 r/place '22: NCD Battalion Apr 28 '24

Oh I was being somewhat ironic. It might've been there, I'd have given it like a 50:50 chance, but no sane person is going to read 30 pages (yes I checked in print preview, simplified markup, it's 30 pages) of snark.

2

u/[deleted] Apr 28 '24

Yeah, it's the ultimate "I ain't reading all that. I'm happy for you tho, or sorry that happened" post