Hacker News new | ask | show | jobs
by 9q9 2209 days ago

   has _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.
1 comments

There 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.

We should be able to write a MIR lint that gives us linear types wherever we want them. I hear they are most useful with unsafe code.
Wow, thanks for this beautiful post! I'm coincidentally about to publish a language similar to Rust which addresses #2, and I'm now seeing perhaps I should improve a lot of these other things!

I've been looking for someone knowledgeable about Rust's benefits and drawbacks, to learn how the language compares and how to best explain it, would you be willing to chat?

(My email is my username @gmail.com, and I'm also on discord at Verdagon#9572)

Thanks!

The C FFI worries me because if I choose Rust for our project I'll need to interface a huge number of C / C++ libraries with the Rust core. OTOH, Mozilla seems to be doing fine mixing Rust with C++.

Its not that it doesn't work, but rather that it is not guaranteed to work, and the design is basically a pile of duck tape. For example, you can use Rust arrays in a C FFI declaration, but that won't do what you think it does:

    extern "C" { fn foo(x: [u8; 42]); }
does not declare the C function

    void foo(uint8_t x[42]);