3 ms·
Rust seems intimidating at first, but under the hood the language semantics are (or used to be, see below) fairly simple. The big borrow checker hurdle comes f
by the_duke 7y ago
Rust seems intimidating at first, but under the hood the language semantics are (or used to be, see below) fairly simple.
The big borrow checker hurdle comes from developers not being used to thinking in/about lifetimes. It takes some getting used to because it enforces constraints that should be there for a non-GC language, but are not enforced anywhere else. But it is a hurdle you can get over after a few weeks of feeling like an idiot. At one point it just clicks. You start to design your types and code with lifetimes in mind, and the borrow checker is not a problem anymore and feels natural - and a helpful safeguard even.
The other complex part is the lack of inheritance and the trait system, which is also foreign for many. I feel like if you had contact with ML languages, and Haskell in particular, you will have a much easier time. It takes a different approach for structuring code.
Sadly Rust is in fact getting mroe more complex all the time though, with things like async/await added and other complex features in the pipeline (like specialization, higher kinded types, generators, ...).
All of those make sense and make the language more powerful, but at the expense of simplicity.
- onebot 7y ago> The big borrow checker hurdle comes from developers not being used to thinking in/about lifetimes. It takes some getting used to because it enforces constraints that should be there for a non-GC language, but are not enforced anywhere else. This!
- roblabla 7y ago> Sadly Rust is in fact getting mroe more complex all the time though, with things like async/await added and other complex features in the pipeline (like specialization, higher kinded types, generators, ...). Some of those have fairly simple semantics though. Generators are really just very a very big enum, with a function moving through the different states of the enum. Async/await is just a fancy name for a generator that returns a Poll. Specialization is very constrained - it only applies to trait methods qualified with "default"), but I agree the current rules around it are rather complex, and they aren't even sound so they'll probably get even more complex. HKT has no concrete plan so it's hard to know how much mental overhead it will add. But it's likely to be very non-trivial too.
- the_duke 7y ago> Some of those have fairly simple semantics though. I'd disagree with that. Yes technically, a generator is just a enum representing a state machine that can be stepped through. And async/await is just a generator with some extra compiler utility. But they completely change how code is executed, to the point that you don't really think about it as a state machine and have to work hard (mentally) to reconstruct what is happening under the hood. Debugging gets much more complex. And it also adds another part that you need to understand and work with. Async/await is particularly bad because it's not a standalone feature: it comes with a large ecosystem of crates that provide streams, executors, reactors, timers, .... It's a huge strain on the complexity budget.
- jandrewrogers 7y agoThe borrow checker is more problematic for systems software than you suggest. It is unable to express lifetime models that are essential for some types of systems software, forcing you to either litter your code with "unsafe" or to sacrifice significant amounts of performance. If performance is critical (it is for my use cases), and achieving that requires disabling memory safety, what other unique value does Rust bring to the table? That is a question to which I have been unable to find an obvious answer. Complexity doesn't bother me (obviously, since I use C++ regularly) but lack of expressiveness is a big issue.
- steveklabnik 7y agoSometimes, safety is faster than unsafety, because it lets you achieve algorithmic speedups in a more maintainable way. For example, Stylo, the new parallel rendering engine in Firefox, was attempted in C++ a few times. It failed each time. However, the project succeeded in Rust instead, and is much faster. Additionally, the goal of unsafe is not to throw away all safety; it's to prove to the compiler something that you know, but it does not know. This means that you can contain the unsafety, and still gain the benefits as a user. The goal is to isolate and contain it, making it easier to write correctly.
- the_duke 7y agoI haven't done a lot of C programming myself, but I've heard very mixed opinions from C devs. Some say they hate the borrow checker because it introduces artificial constraints. Others say that they love it because it enforces how they write their code anyway, but now with compiler checks that prevent them from messing up. I think it's a fair criticism that the borrow checker is sometimes not powerful enough to understand a perfectly valid access pattern, but unsafe is always there if you know better. More often than not (in my experience), the access pattern is not actually valid though, or risky and easy to mess up. And the compiler rubs your nose in a suboptimal design. I've found that when I needed to reach for `unsafe`, I often could restructure the code instead to achieve a safe result with minimal performance loss.
- saghm 7y ago> The other complex part is the lack of inheritance and the trait system, which is also foreign for many I agree that the trait system is less familiar to most programmers than inheritance, but I'm not convinced it's more complex; inheritance itself isn't really something I'd describe as simple, just something that people are more familiar with.