6 ms·
I consider myself a quite expert C++ programmer, and when I program in C++, I do fight with the compiler, constantly. For example: 2000 line template error, so
by fluffything 6y ago
I consider myself a quite expert C++ programmer, and when I program in C++, I do fight with the compiler, constantly.
For example: 2000 line template error, some overload cannot be picked deep in the template instantiation stack. Probably a bug with SFIANE, or template argument deduction, or ADL, or... the error message isn't helpful. The compiler tries hard to give me a "smaller" error message, by filtering out overloads that it thinks aren't relevant. One of those is the one that should get picked. Why isn't that happening? Let's figure it out. Let's start going through the template stack, trying to break the compiler in the right way to give you the right error message. Tricks like:
template <typename T> struct NeverDefined;
NeverDefined<U> {}; // errors spitting out U
int******** k = t; // errors spitting out the type of t
etc. Its a constant fight.
That's the exact opposite of "fighting" rustc. Using rustc feels like pair programming with a god-like developer. They see your mistakes dozens of moves ahead. They give you a nice error message, 10 lines long, with a summary, and an annotated version of the relevant parts of your code, explaining how the different pieces fit together to cause a problem. If you are not sure you fully understood it, you can just ask rustc to explain the error a bit more, and it would give you a couple of "self-contained" examples that produce the same error, so that you can learn from those, and then go back to your big program and fix the issue. Its fighting in the same way that going through university and learning for exams is a "fight".
People who think they know better and there is nothing left to learn from them will see that as a real fight. They'll also see pair-programming as just another fight, a waste of time. Or even will start a fight when somebody elses review their PRs.
If you are that kind of person, then Rust probably isn't for you. You won't enjoy the compiler. You won't enjoy the language. The language is designed by listening to all opinions, listening to how many different people weight them, and then making an engineering decision. That's the complete opposite of "i'm always right, best products are designed by a single person".
Luckily for Rust, most programmers aren't like that. Github is proof that for many, programming is a social activity. Programming with rustc makes you feel less alone, you are always pair programming. Programming is not a fight with the compiler, but a back & forth interactive conversation, in which you together with the compiler are working together to write a bug free program.
When you collaborate in Rust projects with other people, the social experience is also improved. The discussions about dependencies are very "goal-oriented and high-level": is this a dependency that will make the product better? Things like "how do we integrate or maintain this dependency" aren't issues with cargo. You also never discuss about formatting, that's handled for you automatically. You also never discuss about undefined behavior, or what the C or C++ standard say, or whether dereferencing something is UB or not, or whether you need a check here or not. If it compilers, the compiler proves your program for you. A huge refactor in a lock-free multi-threaded program? If it compiles, it has no memory safety or data-races. You can focus on just discussing the underlying algorithms, the architecture, the things that are most relevant for humans.
Just look at github.com/rust-lang/rust itself. People have time to write issues, explaining in them how to fix them for beginners, offering to mentor beginners into fixing them. You can drop a Rust beginner into a compiler, and you just need to explain them the high-level description of the fix. The compiler makes sure that they make no errors, and they can explore that part of the compiler on their own, by just following the types.
Because Rust is not "just memory safety". Its also having very strongly typed and explicitly-typed APIs and function signatures, that automatically give you great docs, great error messages that tell you which types to use, which you can use to find which methods these types have, and just explore code.
And these are just a couple of things that make Rust better than everything else I've ever used.
Is it perfect? Of course not. I've been using Rust full-time 5 years. It has _a lot_ of issues that probably cannot be fixed due to backward compatibility. But these issues are smaller, and less annoying, than the issues that all other programming languages I've ever used have.
Will I use Rust forever? I really hope not. I really hope somebody manages to discover a language that's significantly better than Rust in every way.
But I doubt I'll see that in my lifetime. Given the amount of features in Rust pipeline(), and the amount of time the group of PL design PhDs are taking to get them just right, such a language would need to be a significant advantage over the state of the art.
() specialization, higher-kinded types, generic associated types, variadics, full operational-semantics specification, ... there are even RFCs for dependent-types. It wouldn't surprise me if programming in Rust in 10 years feels closer to programming in Idris2 than in "Rust 2018".
- 9q9 6y agohas _a lot_ of issues I value your perspective on programming languages. Would you mind sharing those issues? I am in a position where I can and do influence PL design.
- fluffything 6y agoThere are way to many probably unfixable issues at this point, from the top of my head: * Lack of proper Linear and !Movable types, and being in a spot where adding them to the language would be a backward incompatible change. This results in complex abstractions, like Pin. * Fallible destructors without an easy way to return failure. In Rust, destructors (Drop::drop) can fail, but the only way for them to fail is to unwind the stack, which doesn't play very well with the rest of the Rust error handling story. C++ gets this a bit better with destructors being noexcept(true) by default, and providing tools to query whether that's the case. * Inconsistent handling of Out-of-memory errors. Initially, Rust decided that OOM errors would be fatal, and settled on using unwinding for them. Then realized that some projects driving Rust design, like Servo, actually needed to handle them (e.g. imagine downloading and image that's too big to fit in memory bringing your webbrowser down..), so they patched being able to handle OOM on top, by adding methods to some of the collections, like `Vec::try_push`, `Vec::try_reserve`, etc. This essentially means that there are two methods to do any operations on collections, that it is pretty much impossible to make sure that code that must handle all OOM errors for correctness does so, and does not call an unwinding method by default, etc. Its also one of the reasons why colelcitons parametrized by allocators (Vec<T, A>) its taking so long. * Lack of parametrized modules, probably a decision that's just too hard to fix at this point. * Lots of lang items for singletons, like global allocator, panic runtime, etc. * Unsafe keyword being too coarse grained. Unsafe functions implying unsafe function body block, unsafe keyword used to allow different types of contracts, from dereferencing a raw pointer, to calling a target-feature function, ... * const and mut raw pointers: this distinction adds pretty much zero value, and complicates certain types of code quite a bit, for no reason. * too monolithic standard library: libcore adds floats suport, which essentially requires you to implement floats for embedded targets, for which they might make absolutely no sense. No way to prevent programs there from using floats, etc. No way to only implement the different parts of the standard library that make sense for a platform, like threading, etc. but instead requiring targets to just mock the standard library with "unimplemented!()" hacks that error at run-time when you try to use certain APIs. * PartialOrd, Ord, PartialEq, Eq, traits are not very mathematically sound. They attempt to achieve what C++ spaceship operator attempts, but fail quite hard. They assume that there is only one "ordering" that might make sense for a particular type, but that's usually not true. For example, floats have many orderings (the classic partial order, and IEEE total order, etc.). * Operator overloading traits like Add or PartialOrd mix half-backed semantics with operator overloading. The standard library ends up, e.g., implementing addition for Strings, to mean concatenation. So essentially any program that constraints generics using the Add trait and requires, e.g., associativity for being free of logic errors kind of fails when passed strings (it will work, but produce garbage). * Iterator::next returns &Target, which means that you cannot implement Target to return a "proxy" reference type. * Many of the standard library algorithms, like "sort" and "unstable_sort" only work on slices, there is no way to use them on Lists or other collections, requiring everybody to re-implement them. C++ got this a bit better. * BinaryHeap is only a min-heap, reusing it as a max-heap is quite weird. In general, these std::collection API mistakes are consequences of having bad fundamental traits (like PartialOrd and friends mentioned above), and a lack of more meaningful Operation traits. * std-library is a mixed bag, with some things being quite good thought out (Result, Option, The smart pointer types, Cell types), and others being kind of an after-thought (operator overloading traits, collections). Method names are also often inconsistently named across the different parts of the library, does not feel very cohesive. * C FFI feels like an afterthought. Some things got bolted afterwards like va_args, unions, packed, etc. Its pretty much not clear at this point how to use these correctly, or whether they can be used correctly at all in C FFI. libc is one of the most used Rust libraries, and its a huge mess of duck tape, ABI incompatible depending on where you run your binaries, which resultes in crashes that are hard to debug, etc. It basically assumes that all operating systems that Rust will ever support never will break their platform ABI. Which is an assumption that no widely-used OS satisfies. How to pass C callbacks that panic, etc. is also still super weird. And probably many more that I can't remember right now. These are just a small fraction of the issues I've encountered and that I believe are not fixable in a backward compatible way. None of them is, in isolation, a big deal. Whether they lead to a death by 1000 paper cuts at some point or not, only time will tell. Rust also has maaany more issues, that are hopefully fixable.