Live data from Hacker News

Tell HN: Rust Is Complex

news.ycombinator.com

91–100 of 102 posts

Re: Tell HN: Rust Is Complex

#92
I've done quite a bit of development in C++. Now learning Rust for fun. My impression is that there is too much unnecessary complexity in Rust language and its type system that it gets in the way when you try express your thoughts in code. While writing Rust code I can't shake the feeling that I can write functionally the same code in C++ (sans the memory safety guarantees of course) without ever struggling with problems like the one described in this post. In fact C++ now starts to look like a simple language to me. I guess everything is known in comparison.

Re: Tell HN: Rust Is Complex

#93
post #59

Earlier quoted context omitted.

I feel your pain as I started to learn Rust recently (again) and found the same problem, coming from C. In my case, after fighting for literally days, the crate OnceCell did the job (kind of), but I felt awkward retorting to an external crate to deal with a simple static-global-initialize-once-never-touch-again piece of data.

Initializing global, static state isn't simple at all -- C++ is a prime example for how a bad design for static initializers can go horribly wrong. At the very least, you need some sort of locking mechanism to deal with the potential issue where multiple threads try to initialize the variable concurrently. And you need some poisoning mechanism to prevent panics and reentrancy during initialization. Rust makes you rea…

I agree, but I have to say that not all the scenarios are the worst case scenario, and sometimes initialization is just "simple", like it was in my case and in other thousand cases I've worked in my life (I do embedded C mostly single-threaded).

This application was also single-thread (a command line utility to parse a text file). I now understand that Rust cannot guarantee the safety of initializing global variables so it makes me take the long road, but it could be great it could infer better about each particular case and enforce only when required.

If I can shoot myself in the foot with C then Rust would be kind-of clamping down on me and nailing my feet to the ground. At the end of the day, it hurts more or less the same.

That was my experience as a rookie with Rust in this particular case.

Re: Tell HN: Rust Is Complex

#94
post #93

Earlier quoted context omitted.

Initializing global, static state isn't simple at all -- C++ is a prime example for how a bad design for static initializers can go horribly wrong. At the very least, you need some sort of locking mechanism to deal with the potential issue where multiple threads try to initialize the variable concurrently. And you need some poisoning mechanism to prevent panics and reentrancy during initialization. Rust makes you rea…

I agree, but I have to say that not all the scenarios are the worst case scenario, and sometimes initialization is just "simple", like it was in my case and in other thousand cases I've worked in my life (I do embedded C mostly single-threaded). This application was also single-thread (a command line utility to parse a text file). I now understand that Rust cannot guarantee the safety of initializing global variables…

Yeah, Rust is built from the ground up for programming at scale. One of the consequences is that it makes you think about thread safety from the start. (Imagine if you're on a large team and not everyone is aligned on whether some part of the code is thread-safe or not.) Some things are harder, but the benefit is that you can often make a program multithreaded, and many times faster on real workloads, with just a few minutes of work (an experience that's unmatched in all of programming).

One of the ways Rust is successful at scale is that the intraprocedural analysis is sophisticated, but the interprocedural analysis is deliberately quite basic. It's not quite in keeping with that to do things like selectively enforce bounds on global variables.

For use cases like the one you mentioned, the general strategy Rust wants you to use is to pass around a context with your data inside it (and the data could be in OnceCells if it's lazily fetched). This is also a more testable design. Global state is meant to be used sparingly.

Re: Tell HN: Rust Is Complex

#95
Use a `Pin` type that doesn't include a generic. It's how I avoid this. Works out quite well! (Including for IO pins; I've seen some `Pin` abstractions that use 3+ layers of nested traits!)

Figure out which parts you like, and use them.

edit: Assumed you were referring to embedded GPIO pins. It seems this is something else.

Re: Tell HN: Rust Is Complex

#97

Earlier quoted context omitted.

Waiters and pollers are artifacts from the fact that Futures in Rust are lazily scheduled, unlike Javascript, for example, where Promises are eagerly scheduled and executed. When you don't want to block your process and program in a reactive, non-blocking way, which is what futures and promises are an implementation of, what you are actually defining is something like this: > "our process will do some synchronous wor…

a minor point, but perhaps coroutines, stackful or not, should be distinguished from threads.

[deleted]

Re: Tell HN: Rust Is Complex

#98
post #81

Earlier quoted context omitted.

> Otherwise you can just wrap everything in Arc, Cow and similar helpers and call it a day. … which is how we got slow, bloated legacy C++ codebases today, should we make the same mistake with Rust? If you are wrapping everything in ref-counted smart pointers it might probably be better to just use a garbage-collected language like Java or C# instead…

As I said: It depends what you are writing. If you write big libraries and major codebases that are meant to represent the foundation for something big, maybe you should not follow my advice. If however you are writing a small tool where good enough is indeed good enough (and still 10 times faster than equivalent python code), who cares. Rust can go anywhere inbetween counting every bit and byte and doing no such thi…

The thing is that languages like C++ or Rust has the potential to become something even slower than Python if you don’t know what you’re doing. For example, (the example is C++ but you can say the same thing about Rust):

https://stackoverflow.com/questions/9378500/why-is-splitting...

Same with smart pointers: Languages like Java and C# have highly tuned garbage collectors that can be faster than reference counting in most situations. The rationale mostly deployed for using RC isn’t simply just raw performance (it can be slower because of the refcount read/write for every dereference), but predictable performance (you don’t need to worry about GC lag spikes, which is important for real-time applications. But even that advantage seems to be disputed.)

Re: Tell HN: Rust Is Complex

#99

I think I need someone to explain Rust async to me. I think I understand that await causes the half of function to be rewritten into a state machine that returns immediately to caller and the promise object implements the rest of the control flow. I don't understand waiters, pollers.

Waiters and pollers are artifacts from the fact that Futures in Rust are lazily scheduled, unlike Javascript, for example, where Promises are eagerly scheduled and executed. When you don't want to block your process and program in a reactive, non-blocking way, which is what futures and promises are an implementation of, what you are actually defining is something like this: > "our process will do some synchronous wor…

Thank you for your effort and time for explaining this to me.

I still need to learn more. I think I'm not sure how someone would write their own runtime with pollers and waiters. Those are the only APIs provided by Rust.

Re: Tell HN: Rust Is Complex

#100

Question: How much of Rust’s type system complexity is due to async? It seems Pin is mostly related to async and the official examples for GAT are for async. Given the complexities of async and how other alternatives such a Java Loom’s virtual threads, in about 10 years I am not sure that we won’t see Rust going all in on async as a mistake.

The GAT complexity is due to async working without extra per-task allocations. If you allowed a heap allocation per each Future (like e.g. JS does for each Promise), it'd be pretty simple, but Rust goes to great lengths to avoid heap allocations.

Rust can merge the entire call tree of async execution (which can be as large as the whole program if async recursion isn't used) into one struct that represents entire state space needed from start to finish. This struct has a static type and a fixed size known at compile time. Describing that type in the type system, including all the generics and lifetimes, is pretty complex.

Pin is a bit of a technical debt. It has been invented to ship async/await without having to first add non-moveable/self-referential types to the language. It is more clever and clunky than a first-class language feature could be. OTOH async/await has been production-ready for a few years now, and non-moveable types are still not a thing, so the trade-off was worth it.

In practice you don't need to even know about Pin if you're just using async/await syntax. It's needed only for lower-level plumbing, like custom runtimes or clever low-level primitives that can't be expressed in normal async code.

Post reply on HN