back

by dochtman·7y ago·view on hn ↗
Have you actually used Rust? I spend very little time (or code) on resource management when writing Rust code. And when I do, it's usually the compiler helping me understand an aspect of my design that doesn't work as I expected. Sure, other languages might allow me to gloss over that, but that will likely come down to finding the edge case in production.
5 comments
Yes - rust type system is very restricted in what it can express because it needs to be verifiable at complie time - this means that some trivial things become a huge pain to describe to rust compiler and rust programmers seem to develop this Stockholm syndrome relationship with it and start singing it's praises on every turn. Memory management in dynamic graph structures with cycles is hard without GC, rust type system is not equipped to deal with this. Although you can still have leaks by holding on to unused references the problem becomes a higher level one and describing the structures/managing them becomes much simpler with a GC.

That guy saying that you can easily avoid GC in C# is also not realistic or doesn't know what he's talking about - even language expressions can lead to object allocation in C# - you really need to know what you are doing to avoid the landmines - it's very clear the language wasn't designed for this - if you need a lot of code like that.

LOL, I'm "that guy" I think... Glad we agree about the complex/cyclic structures at least.

Regarding C#, I said for the most part. I stick by my assertion that (a) GC is quite efficient (esp.w/gen0 objects generated by your "expressions") and far from "lame" and (b) working with value types and Span<> (like Rust Slices) together can significantly reduce GC pressure to the point where it's acceptable, assuming there was actually an issue to begin with. Doing this in C# on hot-paths is certainly much less effort than moving to Rust wholesale, and you haven't thrown out the GC baby with the bath-water.

All the things that you do to make Rust efficient - allocate on the stack instead of the heap when you can, pre-allocate when size is known, use static/nested lifetimes, use slices to owned structures, etc., you can do in C#, but you don't have to worry about it until it actually becomes an issue.

If you're in the kernel or embedded system or the middle of a game rendering loop, then sure, Rust's compiler guarantees make this style programming easier if that's the way you want to go - and Rust has macros and other features that C# lacks. (Although the .Net JITTer is going to generate type-specialized methods and inline them, etc., w/a Rust macro you know up-front exactly what is being generated, which is nice.)

My experience in avoiding C# GC comes from XNA era when Microsoft had this shitty .NET runtime for XBox 360 allowing anyone to develop for it - the GC was so bad you had to be very very careful to avoid it and stuff like using foreach would allocate (AFAIK nowdays it can optimize it away in some cases but not in all and you still need to know when if you want to avoid allocation). People wrote code which avoided GC but it looked nothing like C# and in fact was more verbose than something like Rust or C++ and you were in uncharted territory since no allocation programming is not really C# thing and using high level features would just lead you into invisible traps. So you ended up using preallocated arrays, static functions, globals, etc. etc. in a language with poor generics (you can't even specify an operator as a generic constraint), no top level functions, etc. etc.

I've kept track of .NET progress since then and read about stuff like Span and ValueTask, ASP.NET Core team perf did a good job leveraging those for optimisations (eg. their optimised JSON parser) in such scenarios I agree C# with low level stuff sprinkled in is a good choice

But if your problem domain requires avoiding GC throughout and having better understanding on what the abstractions will compile to pick a language thats designed for that. It's like when I had to review some Java 6 code which tried to work around the fact that Java doesn't have value types with byte arrays - it was just soo bad compared to even C equivalent it was better to rewrite and go trough JNA.

You are right that cyclic mutable graphs are very hard to model in Rust.

But very little code actually looks like that in reality. A lot of code looks like that by accident.

Cyclic mutable graphs are very hard to model in _safe_ Rust (without arena). They can be implemented easily in unsafe Rust, using pointers instead of references, like in C.
They can also be implemented by not using pointer spaghetti ;)

Even in C++ I generally prefer to use handles over pointers for cases like that because graphs are just plain hard to get right, and if you use handles you can get a nice error message when something goes wrong instead of a segfault.

To be fair, I spend the same amount of time on resource management in C++ too (compared to Rust). Unless I'm writing a data structure (happens occasionally) or some explicit resource management layer (texture pool, mesh LOD system, etc), I delegate management/ownership to the system designed to handle it.
I'm a hobby Rust user and I love the language, but I definitely feel the heavy hand of the borrow checker a lot of the times. It's the only language where coding can something seemingly simple can push me to the limit of my abilities and understanding.

However, I usually pick Rust for performance-critical code with quite a bit of concurrency, so the problems involved are inherently tricky. I'm gaining more and more intuition about ownership and rustc-friendly software design, and so I hope I'll be able to better understand if my struggle is due more to limitations of the compiler and Rust's semantics or the limitations of my skills.

For comparison, I've written ~6000 SLOC of Rust in my lifetime, so I do have some experience but I'm definitely not an expert.

I use it occasionally and trying to port an old Gtkmm toy project to Gtk-rs was an eye opener regarding both build performance and the pain of dealing with callbacks in GUI code.

First having neither a build cache, or binary dependencies, means that C++ wins on the "make world" build, because naturally all my third party libraries are already compiled.

Then there is incremental compilation, incremental linking, pre-compiled headers and modules to help with the rest.

GUI code then becomes a fest of Rc<RefCell<>> in event handlers, or using arrays with vector clocks workaround as shown on Catherine's talk.

Could you link that talk?
"RustConf 2018 - Closing Keynote - Using Rust For Game Development by Catherine West"

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

I have a bit -- but I don't have a project that makes sense to do in Rust at the moment. I'm mostly doing long-running symbolic AI code where I don't want to mess around with object lifetime because imposing the concept of "ownership" would be difficult. (F#/C# is the best fit at the moment.)
It takes a little while to wrap your head around it -- I think it took me a solid 3 months before it clicked. Once it clicks though, the lifetime stuff is completely automatic. You really don't think about it. I think because Rust's syntax is so similar to what you might see in a C derived language it adds to the cognitive burden of learning lifetimes the first time around, but IMO, Rust should be the first thing people learn.
> long-running symbolic AI code

That sounds cool. An obvious question is, why not a lisp? But generally do you find f# works well on dotnet mixed with c#?

I love Lisp and Prolog the languages and I went there first -- the issue always turns out to be either (a) they make hard stuff really easy, but stuff that should be easy really hard, and (b) IMHO nothing even comes close to the .Net ecosystem when it comes to debugging tools, libs, etc. Clojure+Cursive comes closest, but the JVM world is a bit of a turn-off for me -- maybe I've been away from it too long, but it just seems a bit clunky. (And of course, reified generics, value types, etc., aren't available in the JVM.) The meta-programming story isn't as good as in Lisp/Prolog of course. JS (as others have said) an acceptable Lisp and has some advantages for the type of work I'm doing -- my problem with JS (and Typescript) isn't the language, but the run-time environment of Node, which makes things like true parallel programming a PITA and incurs serialization overhead between workers.
Thanks fort he reply. I've been looking at f# or ocaml for a symbolic code generation project, but haven't been able to decide.