Hacker News new | past | comments | ask | show | jobs | submit login

Author here. I'm positively surprised, constructive discussion on internet!

There's been some discussion about using Rust.

Rust is an interesting language, and has definitely potential substituting C and C++ in some domains. The main reason I'm not so interested in using it is for game development, is because it's more complicated than C (like C++), and the complications are a bit off from what I'd want (like in C++). Just a quick googling reveals that rust mangles names by default, and doesn't have reflection, so I'd probably be in for a lot of negative surprises. Add that with being indifferent about the absolute security, and soon my code is mostly inside unsafe blocks so that I don't have to spend time convincing the compiler my pointers are safe. Maybe. I haven't done much programming in Rust. There are some nice convenience features though, when comparing to C, but they seem to be rather minor things.

It comes down to choosing between two non-ideal solutions. I value simplicity more than some, so favoring the simpler one feels more natural to me. Sure, when you need the safety then Rust seems like a decent choice.




Only responding to one element of your comment -- but I think that Rust's safety features are over-marketed in my experience. The safety Rust offers is only part of a package that provides high-performance high-level abstractions over functionality that is normally very bit-twiddly in C. So far, my favorite thing about Rust is not its safety, but how easy it is to write good performance software using abstractions that are as convenient as Python (or another high-level language). The safety is just icing on the cake at that point (for me at least).

EDIT: Re: pointers -- Rust is a lot easier to write if you just ignore pointers. Really. Either pass small structs by value or pass references (either immutable or mutable), and let the compiler handle the actual pointer manipulation.


> I think that Rust's safety features are over-marketed in my experience.

I agree with you in the sense that overfocusing on safety has led to things like Andrei's "bulging muscle" criticism, implying that Rust is especially lacking in high-level and metaprogramming features. Compared to C++ and D, it certainly does have a lower feature count in that area (although I'd argue that it's counterbalanced by the fact that Rust holds the line on strong typing for generics and hygiene for macros, whereas C++ and D don't). However, compared to most other languages Rust has very feature-rich generic programming and metaprogramming support. We're not talking "does not support generics" here; we're talking about the difference between the 95% and the 99% metaprogramming use cases. In the overall landscape of industry languages, even just having associated types makes Rust's generics system one of the most sophisticated out there. The only popular languages I can think of with more powerful generics are C++, D, Scala, and Haskell, and the first two sacrifice strong typing.

The main reason for the focus on safety is that it, combined with the lack of GC, is what actually makes Rust unique. Very few industry languages have any features unique to them, but the borrow checker is one such feature. C++ and Swift may get something like it in the future, but Rust has it now, and the entire language and ecosystem is designed around it (and the borrow checker is especially difficult to bolt on to an existing language, because it relies on strong aliasing guarantees). So it's natural that most people have focused on the zero-overhead safety when describing Rust--it's the most salient answer to the question "what can I do with Rust that I can't do with language X?"


Yes. For me, as someone who came to Rust from a non-C/++ background, the appeal of Rust to me is that I get powerful features like an ML-ish type system, iterators, functional programming toys, and more, while still getting C++ or even C-sized performant executables.

Rust to me feels like someone sat down to make a systems language that was actually aware of the last 40 years of programming language development. I don't have to sacrifice expressiveness for performance anymore.

The borrowing and reference safety mechanics are just an extra layer of worry-removal icing on what's already a pretty appealing cake.


I think they are marketed exactly well.

If it wasn't for the uptake of UNIX, we probably would never had to discuss about memory corruption in 2016, other than writing stuff like device drivers.

There is now a whole generation that thinks C was the very first systems programming language, the compilers were as good on day 1 as they are today and it has became some kind of sacred cow.

http://www.itsecdb.com/oval/definitions/product-47/0/Linux-L...

Regarding memory corruption and games, it is how we get around to achieve game cheats.


I don't think Rust's safety guarantees are over-marketed because they aren't important, but because many of the benefits of the language/stdlib/tooling are available to people who don't frequently have to deal with memory corruption. It's not that the safety isn't valuable (I just about pulled all my hair our implementing custom data structures in C++ last time I did it), but that most developers think it's not as useful as other marketable elements of the language.


FYI, if you care as much about compilation speed as your post suggests, today's Rust is right out: rustc is considerably slower than C++ compilers for typical workloads in large part because it doesn't have proper incremental rebuilds. That could change in a matter of months, which I'm looking forward to, but it still probably won't be at C level.* And without optimizations, rustc produces considerably worse object code than even C++.

* I could be pleasantly surprised, though. In theory, for incremental builds, based on the general design planned [1], rustc should be able to do better even than C in a lot of cases because only changed functions need to be recompiled rather than entire files; but I'm a pessimist and expect there will be something to make it slow in practice. I could be wrong though.

[1] https://github.com/rust-lang/rfcs/blob/master/text/1298-incr...


I was positively surprised by the constructiveness of the article itself. You'd usually get Linus' style rant about how C++ sucks so bad and C is the epitome of simplicity and design. I wholeheartedly agree with you that both languages are lacking, and I'm also waiting until Rust or another language grows mature enough to replace both of them in most cases.

But I think most of the main points you describe as impossible in C++ are actually completely possible. It means moving away from the 90's paradigm of using C++ to implement deep class hierarchies with design pattern, but after all C++ is a multi-paradigm language. If you can do data-oriented design in C, you could most certainly do it in C++, and the abstractions C++ provides actually make it easier.

In essence, I would separate game objects to a logic instance and state objects (pure structs) and use smart pointers with a generation-counter to point the logic instance, which in turn would have smart a pointer to the state struct. The smart pointer would overload the dereference operator and transparently update the logic instance to get the new vtable if needed.

This decoupling of state and logic could do many nice things, such as serializing the entire game state in a very clean way, and pure state structs would not be harder to parse than C structs (they would essentially be C structs), so you can still have your memory editor.

The main difference between C and C++ here would be the cost abstractions, both cognitive and performance-wise. I have to admit I've never ran into standard abstractions significantly slowing optimized debug code, except for standard library containers. I'm not a game programmer though, so YMMV. I get the cognitive cost argument, but for me the cognitive cost of C (namely having boilerplate noise scattered all over hiding the interesting code and having to be super-extra-careful with memory management) is higher than than the cognitive cost of internalizing all the layers of abstraction in C++.

If you're programming a network game, I still think you'd do your users a better service if you don't dismiss safety offhand. There's a whole class of memory-safety bugs which would never surface during normal play, but could still be exploited with specially crafted packets. Of course, C++ wouldn't give you perfect protection either.


I agree with your depiction of C++. In fact, one of the benefits of C++ is that it is convenient to use stack allocation rather than heap allocation. This can really simplify memory management. The trick is that you have to choose a different idiom for your idiomatic C++ ;-). The trick is to always know who owns the memory and to never allocate memory in libraries. You use dependency injection, always pass by reference and always clean up in your destructors. If you are forced to allocate something in a library, you build a wrapper to deallocate it (or sometimes copy it when you receive the memory so that you can own it).

It takes some experience to build applications this way, but it is well worth building that experience. C++ is still my language of choice for anything that requires fine control of memory and attention to performance. Rust looks like a very possible successor, but I agree that the lack of incremental compiling makes it not useful for large projects at the moment.


> I agree that the lack of incremental compiling makes it not useful for large projects at the moment.

Whoa there. Let's not exaggerate the scope of the problem. Large projects in Rust usually consist of many crates (in Servo, 150+). Rust absolutely has incremental compilation on the level of the crate, so the compilation times are similar to what you see in C++ with per-directory unity builds. This is the same story as Go, for example, and people don't say Go isn't suitable for large projects; incremental compilation isn't even on Go's roadmap.


> one of the benefits of C++ is that it is convenient to use stack allocation rather than heap allocation.

Not quite. With its destructors (and the RAII that come with them), C++ makes it easy to follow a scoped discipline. Allocations are easily scoped, but they don't necessarily happen on the stack.

Strings and vectors for instance, are typically allocated on the heap, assuming you're using the default allocator (which most people do anyway). While this is convenient and make things simpler, that's quite the runtime overhead, and causes latency problems similar to those of a genuine garbage collector (malloc() and free() aren't exactly constant time).

> never allocate memory in libraries

Now that can reduce the number of allocations in a program. Note that this also rules out most of the STL: its containers call their (possibly user defined) allocator themselves.


It is quite possible that I'm out of date (haven't used C++ in anger for the better part of a decade), but local variable allocations have always historically been on the stack. Just don't use new. Arrays will be allocated on the stack as long as they are fixed length.

And yes, this rules out the STL :-(


> local variable allocations have always historically been on the stack

And they still are, thank goodness. My point was that as soon as you call a constructor, all bets are off: for instance, merely declaring an std::string allocates on the heap, local variable or no. But it seems you already knew that.

I think it is important to distinguish scoped discipline from stack allocation. The former is a way to program. The later is an implementation detail (modulo performance). Your wording was a tiny bit sloppy, so I jumped at it.


Good that I succeeded at avoiding ranting. I find it really hard to keep from throwing absolutes.

Almost everything is possible with C++, that's true. Some things require a lot of engineering though. Like full program reflection, fast debug builds, and fast (below 5s) builds.

Funny that you mention the decoupling of state and logic, because I see that as a non-issue in C. State is a struct, logic is a function. There would need to be a really convincing argument to make me wrap the hundreds of game object components I'll have to two objects each.

C has some differing cognitive cost, which C++ doesn't have, that's for sure. I think we have just differing personal preferences on which is worse :P (There are real-world situations where I'd choose C++ though)

The network point is valid. Implementing it was mostly a nice learning opportunity. I haven't yet decided if I want to keep it or not. It currently lacks safety and proper compression, so if I get serious about it I'll probably make all network data go through validation functions, which helps with both compression and handling hostile data.


You've said most of your build time was linking, so wouldn't you get the same build times with C? Templates would slow your build time a lot though, so they come with a price. C++ would never beat C on build times, but C never came close to Pascal, back in the day, and I'm pretty sure C# still beats the guts out of C with hands tied. :)

> Funny that you mention the decoupling of state and logic, because I see that as a non-issue in C.

I beg to differ. Decoupling logic and raw data is not an issue in C, but state is not the same as raw data. You may have the same state data duplicated in memory (e.g. an index), and there's also the issue of representation of state which is not portable (e.g. pointers, particular data structure). You also have pointers to functions (logic) in your data if I understood you right.

With C++ (or any sufficiently high level language) you can easily abstract away the difference between state and raw data. If you've got function pointers or pointers to instances of logic objects (which is a must if you don't want to litter your code with long switch clauses), you could save them as type IDs or something akin to that.

You might be programming something very different, but so far most good C code I've seen was thoroughly objected-oriented. Not in the sense it religiously followed the 3 pillars of OOP, but in the sense it coupled structs with several functions that have been meant to use on solely with these structs and sometimes employed polymorphism by embedding function pointers in these structs.

All of that is much easier to do with C++ classes, and if this is all you wanted (and you don't think you need proper separation of data from bone fide state), you can have a code that would refresh the vtable pointer in memory, and would sure be more straightforward than manually updating function pointers all over the place.

> It currently lacks safety and proper compression, so if I get serious about it I'll probably make all network data go through validation functions, which helps with both compression and handling hostile data.

Validation is nice, but you can't foresee every weird way your data could be shaped. It's very easy to miss one small corner. That's why you see several security engineers going around these threads dissing C. :)


> You've said most of your build time was linking, so wouldn't you get the same build times with C?

C++ generally incurs a price on link times too. For example C++'s stronger type system creates more burden on the name mangling, like there is actually a difference between an int and long, even if they are the same size on the system. Then if you have code that doesn't make this distinction, overloaded functions need to be generated which creates more generate code bloat. Then if you throw templates into the mix, C++ generates a ton of symbols for every permutation. This is why C++ object files are usually much larger than C object files and why the linking process is so much slower.


This is a nice talk from Jonathan Blow (Braid's author) about the necessity of a new language for game programming as an alternative to C++, and why new languages like Go or Rust are not apt to the task:

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


He barely mentions Rust and only to dismiss it as a "big idea language" because it's memory-safe. Not a very interesting critique, IMO- he doesn't talk at all about any of the other features of Rust that improve on C and C++.

Some interesting ideas otherwise, though.


I doubt you'd have the sort of trouble with pointers and unsafe blocks that you suggest. Most typical pointer patterns don't even require lifetime annotations, let alone bypassing the borrow checker.

I also think the "convenience features" are a pretty big deal. Generics with traits are a pretty big value add for their complexity, and to me affine types take away a lot of the complexity of C.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: