Live data from Hacker News

Leaving Rust gamedev after 3 years

loglog.games

861–870 of 996 posts

Re: Leaving Rust gamedev after 3 years

#861

Earlier quoted context omitted.

I really hope that C++ evolves with gamedev and they become more and more symbiotic. Maybe adoption of rust by gamedev community isn't the best thing to wish to happen to language. Maybe it is better to let other crowd to steer evolution of rust, letting system programming and gamedev drift apart

I think I don't know a single gamdev who's fond of "modern C++" or even the C++ stdlib in general (and stdlib changes is what most of "modern C++" is about). the last good version was basically C++11. In general the C++ committee seems to be largely disconnected from reality (especially now that Google seems to be doing its own C++ successor, but even before, Google's requirements are entirely different from gamedev…

C++17/20 are light-years beyond C++11 in terms of ergonomics and usability. Metaprogramming in C++11 is unrecognizable from C++20 things have improved so much. I hated C++ before C++11 but now C++11 feels quite legacy compared to even C++17. The ability to write almost anything, like a logging library, without C macros is a huge improvement for maintainability and robustness.

Most of the features in modern C++ are designed to enable writing really flexible and highly optimized libraries. C++ rarely writes those libraries for you.

Re: Leaving Rust gamedev after 3 years

#862
post #809

Earlier quoted context omitted.

Ok, I'll give you fold expressions and structured bindings as actually important language updates. The rest are mostly just tweaks that plug feature gaps which shouldn't have existed in the first place when the basic feature was introduced in C++11 or earlier. IMHO by far most things which the C++ committee accepts as stdlib updates should actually be language changes (like for instance std::tuple, std::variant or st…

[flagged]

You should probably tone done your speech, and lay off the patronizing attitude, no matter how well justified are your artguments.

Re: Leaving Rust gamedev after 3 years

#863
post #509

Earlier quoted context omitted.

For me, the closest language currently is F#. The open-source ecosystem is not as massive as Go's or the JVM's, but it's not niche either. F# runs on .NET and works with all .NET packages (C#, F#, ...). If the .NET ecosystem can work out for you, I recommend taking a closer look at F#. F# allows for simple code, which is "functional" by default, but you're still free to write imperative, "side-effectful" code, too. I…

Do you know of or have any shareable (sample) projects implemented in your way of doing F#? It sounds very intriguing to me

While the libraries and techniques I mentioned above seem to be well-known, I couldn't find a good public sample project.

I can recommend https://fsharpforfunandprofit.com/ as a starting point.

If there's interest, I can split some of my code into stand-alone chunks and post my experience of what worked well and what didn't.

I wanted to share some thoughts on here on what brought me to F#. Maybe this can serve as a starting point for people who have similar preferences and don't know much about F# yet.

A big part that affects my choice of programming language is its type system and how error handling and optionality (nulls) are implemented.

That "if it compiles, it runs" feeling, IMO, isn't unique to Rust, but is a result of a strong type system and how you think about programming. I have a similar feeling with F# and, in general, I am more satisfied with my work when things are more reliable. Avoiding errors via compile-time checks is great, but I also appreciate being able to exclude certain areas when diagnosing some issue.

"Thinking about programming and not the experience" the author lamented in the blog post appears to be the added cost of fitting your thoughts and code into a more intricate formal system. Whether that extra effort is worth it depends on the situation. I'm not a game developer, but I can relate to the artist/sound engineer (concept/idea vs technical implementation) dichotomy. F#'s type system isn't as strict and there are many escape hatches.

F# has nice type inference (HM) and you can write code without any type annotations if you like. The compiler automatically generalizes the code. I let the IDE generate type annotations on function signatures automatically and only write out type annotations for generics, flex types, and constraints.

I prefer having the compiler check that error paths are covered, instead of dealing with run-time exceptions.

I find try/catches often get added where failure in some downstream code had occurred during development. It's the unexpected exceptions in mostly working code that are discovered late in production.

This is why I liked Golang's design decisions around error handling - no exceptions for the error path; treat the error path as an equal branch with (error, success) tuples as return values.

Golang's PL-level implementation has usage issues that I could not get comfortable with, though:

  file, err := os.Open("filename.ext")
  if err != nil { return or panic }
  ...
Most of the time, I want the code to terminate on the first error, so this introduces a lot of unnecessary verbosity.

The code gets sprinkled with early returns (like in C#):

  public void SomeMethod() {
  if (!ok) return;
  ...
  if (String.IsNullOrEmpty(...)) return;
  ...
  if (...) return;
  ...
  return;
  }
I noticed that, in general, early returns and go-tos introduce logical jumps - "exceptions to the rule" when thinking about functions. Easy-to-grasp code often flows from input to output, like f(x) = 2*x.

In the example above, "file" is declared even if you're on the error path. You could write code that accesses file.SomeProperty if there is an error and hit a null ref panic if you forgot an error check + early return.

This can be mitigated using static analysis, though. Haven't kept up with Go; not sure if some SA was baked into the compiler to deal with this.

I do like the approach of encoding errors and nullability using mutually exclusive Result/Either/Option types. This isn't unique to F#, but F# offers good support and is designed around non-nullability using Option types + pattern matching.

A possible solution to the above is well explained in what the author calls "railway oriented programming": https://fsharpforfunandprofit.com/posts/recipe-part2/.

It's a long read that explains the thinking and the building blocks well.

The result the author arrives at looks like: let usecase = combinedValidation >> map canonicalizeEmail >> bind updateDatebaseStep >> log

F# goes one step further with CEs, which transform this code into a "native" let-bind and function call style. Just like async/await makes Promises or continuations feel native, CEs are F#'s pluggable version of that for any added category - asynchronicity, optionality, etc..

With CEs, instead of chaining "binds", you get computation expressions like these: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo... https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo...

Everything with an exclamation mark (!) is an evaluation in the context of the category - here it's result {} - meaning success (Ok of value) or error (Error of errorValue). In this case, if something returns an Error, the computation is terminated. If something returns an Ok, the Ok gets unwrapped and you're binding TValue.

I have loosely translated the above example into CE form (haven't checked the code in an editor; can't promise this compiles).

  let useCase (input:Request) =
   result {
      do! combinedValidation |> Result.ignore
      // if combinedValidation returns Result.Error the computation terminates and its value is Result.Error, if it returns Ok () we proceed
      let inputWFixedEmail = input |> canonicalizeEmail
      let! updateResult = updateDatabaseStep inputWFixedEmail // if the update step returns an Error (like a db connection issue) the computation termiantes and its value is Result.Error, otherwise updateResult gets assigned the value that is wrapped by Result.Ok
      log updateResult |> ignore // NOTE: this line won't be hit if the insert was an error, so we're logging only the success case here
      return updateResult
   }
In practice, I would follow "Parse, don't validate" and have the validation and canonicalizeEmail return a Result. You'd get something like this:

  let useCase input =
   result {
      let! parsedUser = parseInput input
      let! dbUpdateResult = updateDatabase parsedUser 
      log dbUpdateResult |> ignore
      return updateResult
   }

  let parseInput input =
   result {
      let! userName = ...
      ...
      return { ParsedRequest.userName = userName; ... } // record with a different type
   }
This setup serves me well for the usual data + async I/O tasks.

There has been a range of improvements by the F# team around CEs, like "resumable state machines" which make CEs execute more efficiently. To me this signals that CEs are a core feature (this is how async is supposed to be used, after all) and not a niche feature that is at risk of being deprecated. https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0...

Re: Leaving Rust gamedev after 3 years

#864

Earlier quoted context omitted.

I really think the problem of Rust is the borrow checker. Seriously. It is good but it is overkill. You have to do and plan all things around it and discourages a lot of patterns or makes them really difficult to refactor. I would encourage people to understand Hylo's object model and mutable value semantics. I thinks something like that is far better, more ergonomic and very well-performing (in theory at least).

You can use unsafe code and pointers if you really want, but code will be unsafe, like C or C++.

Look at Hylo. Tell me what you think. You do not need all that juggling. Just use value semantics with lazy copying. The rest is handled for you. Without GC. Without dangling pointers.

Re: Leaving Rust gamedev after 3 years

#865
post #199

Earlier quoted context omitted.

Someone who has experienced real problems as a result of a specific mechanism is not required to solve every single problem with alternatives to that mechanism before saying "this mechanism has caused me real problems and it'd be nice if there were a better alternative that didn't cause those problems". > The moment you allow this, you have to find a way to pick between several implementation - and they don't always…

Disclaimer: I'm aware you guys are working on relaxing orphan rules, and I wish you the best of luck. But as an outsider, orphan rule doesn't seem to be going anywhere soon. And if the original poster had said that I would be ok. Instead what they said is: > It's a great example of something I'd call "muh safety", desire for perfection and complete avoidance of all problems at all costs, even if it means significantl…

> This implies the writer didn't assume what happens if you "turn-off" orphan rules

It implies that the writer would prefer using a different language, not that Rust would be better if it was all the same but with the parts he doesn't like taken out

Re: Leaving Rust gamedev after 3 years

#866

Earlier quoted context omitted.

TBF, unsafe Rust still enforces much more correctness than C or C++ (Rust's "unsafety" is more similar to Zig than C or C++).

TBF this is not really true. Unsafe Rust is a lot harder than comparable C/C++, because it must manually uphold all safety invariants of Safe Rust whenever it interacts with idiomatic Rust code. (These safety invariants are also why Safe Rust can often be compiled into better-optimized code than the idiomatic C/C++ equivalent.)

I wonder if Rust is killing flies with canons (as we say in spanish). There are perfectly safe alternatives or very safe ones.

Even in a project coded in Modern C++ with async code included, activating all warnings (it is a cards game) I found two segfaults in like almost 5 years... It can happen, but it is very rare at least with my coding patterns.

The code is in the tens of thousands of lines of code I would say, not sure 100%, will measure it.

Is it that bad to put one share pointer here and there and stick to unique pointers and try to not escape references? This is ehat I do and I use spans and string views carefully (you must with those!). I stick to the rule of zero. With all that it is not that difficult to have mostly safe code in my experience. I just use safe subsets except in a handful of places.

I am not saying C++ is better than Rust. Rust is still safer. What I am saying is that an evolution of the C++ model is much more ergonomic and less viral than this ton of annotations with a steep learning curve where you spend a good deal of your time fighting the borrow checker. So my question is:

- when it stops being worth to fight the borrow checker and just replace it with some alternative, even smart pointers here and there? Bc it seems to have a big viral cost and refactoring cost besides preventing valid patterns.

Re: Leaving Rust gamedev after 3 years

#867

Earlier quoted context omitted.

So far I am way less productive in rust than in any language I've ever used for actual work, so to rewrite an entire game engine would seem like commercial suicide.

"so far" is doing a lot of heavy lifting there =) I was the same the first two times I tried to use rust (earnestly). However, one day it just "clicked" and my productivity exceeds that of almost anything else, for the specific type of work I'm doing (scientific computation)

I think we shouldn't expect any language to lead different programmers to the same experiences. Rust has the inital steep learning curve, and after that it's a matter of taste whether one is willing to forge on and turn it into a honed tool. Also, I think it's clear that Rust excels in some fields far more naturally than in others. Making blanket statements about how Rust, or any language, is (un)productive is a disservice to everyone.

Re: Leaving Rust gamedev after 3 years

#868
post #173
post #107

Earlier quoted context omitted.

I know rust, I don't know game development (I've dabbled slightly). If I choose to build a game I either need to make it work in rust* or I need to learn a new language (Unity -> C#, Unreal -> blueprints, Godot -> gdscript). So your advice to "just use Unity/Unreal/Godot" is the opposite of your advice "you should stick with what you know" in my case. I suspect the former is good advice, and the latter is therefore w…

Perhaps the more appropriate advice is : Use the right tool for the job. Use C++ for writing a high performance library or a database engine. Use Go or Java for writing a server. Use C for writing a kernel module. Use shell scripts for automation. Use python for trying out ML ideas or heavier duty scripts. Use Rust for ... I'm not quite sure what it's the right tool for yet. I suspect it's trying to become the right…

> a high performance library or a database engine

> a server

Rust is a great tool for these. The focus on performance and reliability (versus fast iteration) is a perfect fit for these domains specifically.

Re: Leaving Rust gamedev after 3 years

#869

Earlier quoted context omitted.

I think I don't know a single gamdev who's fond of "modern C++" or even the C++ stdlib in general (and stdlib changes is what most of "modern C++" is about). the last good version was basically C++11. In general the C++ committee seems to be largely disconnected from reality (especially now that Google seems to be doing its own C++ successor, but even before, Google's requirements are entirely different from gamedev…

C++17/20 are light-years beyond C++11 in terms of ergonomics and usability. Metaprogramming in C++11 is unrecognizable from C++20 things have improved so much. I hated C++ before C++11 but now C++11 feels quite legacy compared to even C++17. The ability to write almost anything, like a logging library, without C macros is a huge improvement for maintainability and robustness. Most of the features in modern C++ are de…

Heh, mentioning metaprogramming and logging is not exactly how you convince anybody of superior ergonomics and usability.

Re: Leaving Rust gamedev after 3 years

#870
post #327

Earlier quoted context omitted.

Absolutely. Async/await typically improves headroom (scalability) at the cost of latency and throughput. It may also make code easier to reason about.

I disagree with this, you're probably not paying much (if at all) in latency or throughput for better scaling. What you're paying for with async/await is a state machine that describes the concurrent task, but that state machine can be incredibly wasteful in size due to the design of futures and the desugaring pass that converts async/await into the state machine. That's why I said it's not "zero cost" in the loosest…

That is true. Rust's async/await desugaring is still missing optimizations. I think that will be ironed out eventually. What mainly concerns me about async/await is that, even with Rust's best efforts, the baseline complexity will probably always be somewhat higher than for sync code. I will be pleased if the gap is minimized and people only need to reach for async when they want to. Right now, the latter isn't the case because of the "virality [of] function coloring".
Post reply on HN