If you don't like Rust, that's A-OK. If you can't quite put your finger on why you don't like Rust, that's fine too. But please don't pass off your vague concerns as substantive criticism.
So you praise Go for having structures and interfaces, but you criticize Rust for having structures and interfaces because that's "too weird".
> The enum approach to variant records is too weird.
Why? Because of the keyword? Swift uses that keyword too!
> The error handling is too weird.
What would you rather see?
> The half-functional style is too weird.
I can't think of any way that Rust differs in this regard from, say, Ruby. Or JavaScript.
> The widespread use of closures for simple stuff is too weird.
Again, it doesn't differ from Ruby. (Actually, Rust uses closures/blocks quite a bit less than Ruby does, because of the pull-style iterators...)
> There's too much unnecessary originality.
Such as? None of the features you've mentioned are at all original.
1. Removing structs and interfaces (in favor of what? Java-style classes and interfaces?) would make Rust more verbose, less of a systems language, and slower at runtime due to the indirect dispatch.
2. Changing the keyword "enum" to something else is a cosmetic change which would make as many users unhappy as it made happy. (We know, because we solicited feedback on the keyword before selecting it!)
3. I've addressed error handling in the sibling comment. There's basically no other way to do it that would be in line with Rust's goals as a systems language.
4. Rust would be a worse language if it lacked closures. Closure support is a basic feature in a modern language.
Sometimes I think CRust would be a good idea, the minimal ideas that I like from Rust just layered on top of C as a linter.
The point is that these ideas are not "weird"—they're just decisions taken straight from other languages.
I can't believe we have to defend closures, of all things, from the charge of being "weird" because a language from the 1970s didn't have them, but here we are.
> Sometimes I think CRust would be a good idea, the minimal ideas that I like from Rust just layered on top of C as a linter.
That is not possible. You can't just graft the borrow check from one language onto another language that was not designed for it. For example, C doesn't even have generics, and the lifetime system is designed around generics.
Honestly, I'm tired of responding over and over to the suggestion that we should have done Rust as a static analysis on C++. If you actually try to design such a system, it becomes obvious very quickly that it cannot work for systematic reasons.
Rust is a systems programming language that runs blazingly fast,
prevents segfaults, and guarantees thread safety.
In a systems programming language I can do without closures, without generics, without .... I would and will say the exact same thing about Swift which also provides closures, generics, ....Us systems programmer types, we tend to drag our knuckles a lot. Still we do have opposable thumbs for grasping tools and we're damned proud of that. Perhaps if and when we sufficiently evolve and attain an upright, high level posture we'll see the light. But for now, be happy that we're trainable if not educable.
Now please excuse me, but I have to go pick some low hanging fruit. That and worse is a lot better.
>What would you rather see? Exceptions
Go also backed into adding most of the machinery for exceptions by adding catch with unwind for panics. If you're going to have the unwinding machinery, why not have real exceptions?
Exceptions aren't so bad if you do them the way Python does, using a predefined exception hierarchy. Catching an exception catches everything in its subtree. Catching RuntimeError catches most of the things one can induce from outside the program. Python's WITH statement also has one of the few mechanisms which can gracefully handle an error on releasing a resource. Exceptions in C++, Java, and Javascript are more troublesome.
[1] https://github.com/rust-lang/rfcs/blob/master/text/1236-stab...
In any case, panics are not guaranteed to unwind or be catchable: they can be turned into aborts (yes, on the stable compiler). This means an arbitrary library using them for recoverable error handling is incorrect.
A WITH clause mechanism implies no dynamism.
Please be more specific with your complaints. Thanks.
Additionally, unless one is using explicitly typed exceptions (i.e. Java-style checked exceptions, very much unlike Python), it requires allocations, as it's not possible to statically determine the size of the error object to be returned.
Similarly, from an assurance/reliability point of view, the dynamically typed nature of non-checked exceptions is unfortunate.
(These second two are, I believe, the points I made last time we discussed Python's exceptions.)
"We'll also need a way to handle out-of-memory (OOM) conditions. The standard library calls the abort intrinsic, which just calls an illegal instruction to crash the whole program. The reason we abort and don't panic is because unwinding can cause allocations to happen, and that seems like a bad thing to do when your allocator just came back with "hey I don't have any more memory"."[1]
Allocating is definitely a big deal for a pervasive error handling! For instance, parsing a number might not succeed. Both the actual parsing and the failure cases are cheap, but not if the latter has to be indicated by allocating and unwinding and doing a pile of the dynamic checks up the stack.
Additionally, whether the language is able to operate in OOM isn't the only reason to choose an error handling scheme. However, even if it was, I don't think it applies directly to Rust: Rust-the-language (and Rust-the-core-library) can operate in an OOM condition, e.g. people run Rust code without needing a dynamic allocator at all. You are correct that some parts of the standard library don't handle OOM, but, using exceptions pervasively means that there is one error handling scheme for things that can rely on having an OS/malloc and one for things that don't need them.
> Besides, unwinding already causes allocation
Yes, and that is one reason why unwinding is not used as a pervasive error handling scheme in Rust: there are various fairly fundamental problems with it. It is one of the reasons why panics can be switched to abort.
Exceptions are not meant for "pervasive error", they are for exceptional execution paths. If an execution path is frequent enough, wether it's an "error" or not, then it should not be an exception.
I think this is the part of the argument that the "no exception camp" usually misses : there are multi type of errors (recoverable vs fatal, frequent vs unfrequent, expected vs unexpected), having exception is not about using exceptions for all of them
If exceptions are just "an option" that a library can choose when designing its interface, then you'll get a split ecosystem: a bunch of libraries that do use exceptions for error-handling, and then a separate bunch of libraries that do the same thing, but don't use exceptions for error handling, which is wasted, redundant effort.
Agreed. Point in case: Have a look at D and the discussions about their GC.
I haven't kept up much about the last year, but my general impression runs down to: If you want to use their standard library. There's gonna be garbage collection. Period. If you want to avoid it, enjoy implementing its functionality all by yourself.
With rust and exceptions you would likely not have a problem with the main ecosystem, but the same problem is bound to pop up with some big & famous library.
Edit: Similarly, exceptions are one of the reasons C++'s SG14 exists.
Rust's uses algebraic types to make things checked at compile time and uses operators like `?` to give you expressiveness while removing the overhead and annoyance of manually checking everything. It is a huge improvement.
Catchable with: https://hackage.haskell.org/package/base-4.9.1.0/docs/Contro...
If Rust is going to bill itself as a systems programming language, it needs these. I added them for my app:
extern {
#[link_name = "llvm.setjmp"]
pub fn setjmp(a: *mut i8) -> i32;
#[link_name = "llvm.longjmp"]
pub fn longjmp(a: *mut i8, b: i32) -> ();
}
It took me awhile to figure this out (and it barely works). There's a cleaner way (adding a wrapper) that is even more work.Sure, but what I meant is that most of the time userspace programs have nothing interesting they can do with the bus error (and, as I noted, standard C does not actually provide this option anyway). Regardless there are existing signal handling libraries that aren't that hard to use in Rust (you now describe one in your post), so I'm not sure why you're pointing to that as "something... that Rust lacks."
There's no way I'm aware of that setjmp / longjmp could possibly be integrated with safe Rust, but I agree that they should be available to unsafe code (in a way that doesn't require invoking llvm intrinsics).
And that's okay. There is a ton of space outside of that context, and still within the "systems" space that C and C++ could use a high quality competitor in, and Rust fits that bill nicely, in my view.
Rust the language is really composed of two almost identical subsets, "safe" one and "unsafe". Safe Rust is what you will normally see, governed by normal safety rules and abstractions. Unsafe Rust is not what you will normally see, but still governed by safety rules and abstractions, only with an escape hatch. What I feel is that safe Rust covers the higher-level subset of "systems programming" (predictable performance, strong abstraction and safety), while unsafe Rust covers the lower-level subset of "systems programming" (excellent performance, near-complete control over everything). And still they are almost identical, so that the abstraction made in unsafe Rust is usable in safe Rust, and that's ideally how it covers the entirety of "systems programming"---the only border is the abstraction itself. Of course, provided that we have enough supply for appropriate abstractions (the community is trying hard with several promising results though).
Rust programmers do like the safety guarantee of safe Rust and rarely talk about unsafe Rust, but I think unsafe Rust plays a large role in the possibility of Rust. It's much closer than the border between, say, "glue" languages and their implementation languages. We don't change the fact that we will sometimes have to bend the rules (it's probably impossible). Instead we let you bend the rules, but only when you are in the cage. And that cage is, while not immunable to every attack, really strong.
[1] The advocacy naturally advocates for something's possibility and not for something's success. So it is still correct that Rust still lacks some solutions for existing problems, though it's not inherent.
I think that's a misunderstanding of meaning. Rust's safety and unsafe are intrinsically linked. It's not that Rust is always safe, but that you have a well defined way to compartmentalize safe and unsafe portions of the program, and can use that to reason better about what's going on. In a way, talking about the safety of Rust is specifically talking about unsafe.
> then all of my carefully crafted error-handling routines would go up in flames
No just sandbox the code inside a try catch...
> pattern matching is the best method of clearly displaying intents and possible downfalls.
You are stating an opinions as a fact here.
This works fine if it's just a matter of rolling back a database transaction or releasing file handles (b/c someone else is doing all the work for you). But if you have more complicated invariants, your try catch has to handle restoring those invariants, no matter what path your code took.
The most important feature is that catch blocks are executed before the stack is unwond, providing the ability for a handler to signal back to the context where the error occurred how it should be handled.
It's sad that no other languages implement this and instead simply decide that exceptions are bad.
The code expander has an entry point which allows the caller to be informed about what variables and functions are free in a block of code. The implementation of this entry point works by intercepting warning exceptions about unbound symbols, and accumulating them in in lists, which are then returned to the caller.
This is a useful thing which reduces the need for users to write their own code walker in certain kinds of advanced macros.
This sounds like a general errors handling issue that nor exceptions nor monadic errors types can prevent.
* panic + catch_unwind, but that's special and not meant or used in practice for error handling
Thinking about it in that sense, classical inheritance has always been based on a fundamentally flawed abstract model.
(Admittedly, purely noun-to-noun inheritance does have its place in a well-structured abstract model, in cases like "every Teacher is a Person", but I stand by Rust's decision to omit it... at least in the near term, because developers are too used to overusing/abusing it out of habit.)
"Everything is an expression" is pretty different. You can say ML/Lisp, but those are non-top-10 languages/styles.
> "Everything is an expression" is pretty different.
No, in Ruby, everything is also an expression; this is sometimes unrecognized because some constructs that are cognates to constructs that are statements in other languages are frequently (but inaccurately) referred to as statements in Ruby (the most frequent example I've seen is "case statements").
Also Ruby is a kitchen sink language, so I don't know if we should be using it as the baseline for a language with a small surface area.
Alternative view: the clean separation of data from API has a nice analogy to the manipulation of data in C, and a powerful way to maintain that separation while still allowing for powerful object oriented programming where appropriate.
> The enum approach to variant records is too weird.
I disagree. The tagged union is an oft-used pattern in C and C++ code. Maybe calling it "enum" is a bit off, but the concept is incredibly useful, and Rust's compile-time support for it is incredibly useful and powerful in my opinion.
> The error handling is too weird.
It's very C-like, but more strongly typed and, thanks to the Rust developers' efforts, quite clean. A C programmer, or a C++ programmer who sets the "no exceptions" flag as a matter of best practice should feel quite comfortable with Rust's error handling.
> The half-functional style is too weird. The widespread use of closures for simple stuff is too weird.
I would substitute "cute" for weird, but overall I don't mind it. I do agree that emphasis on making these features more prominent, especially at the expense of more explicit (e.g. explicit for-loop) constructs, might hinder traction.
I thin kit depends on both where the new users are coming from, and how much they've modified their thinking to a specific programming style. If you are a C or C++ programmer and already think in conceptualized for loops when planning steps, then it seems odd. If you haven't gone that far into letting that style define your thoughts, or continued to use other languages that were a bit more functional, you might find that that conceptually it's easier to reason about.
I.e. "I'm going to transform each of these items by doing X, Y and Z and store them here" as a concept is neither directly a for loop nor a map a the basest level, but you might start thinking in maps or loops after a while if that's your only option.
It's equivalent to writing C++ with:
* No structural inheritance (i.e. no inheritance of data)
* Inheritance only from pure virtual interface classes
* MyCppPureVirtualClass * pointers in various interfaces
Except that traits are also usable for static dispatch as in templates/concepts.This isn't the most common style of C++, but it's not uncommon either, and I happen to like it a lot; it's all over Cef's and Chromium's code, for example.
> The enum approach to variant records is too weird.
What's weird about it? It's just the same as e.g. Haskell, ML, but with a different (arguably slightly overloaded) keyword. Swift uses the same terminology.
> The error handling is too weird.
Eh. I certainly like the way that explicit error handling is "forced" on the caller with mechanisms like Result<T,E>::unwrap. But I agree that magical things like the question mark operator are a little too cute.
> There's too much unnecessary originality.
FWIW, I can't think of much real originality in Rust. It just brings together "exotic" features from lesser-known languages.
I think you're right. Except for the borrow checker, which was a major breakthrough.
Edit: As an aside, I found a Reddit post the other day asserting that the Cyclone developers knew that it would never become mainstream but thought it was important because other languages would adopt it's ideas. It's amazing that they predicted that regions would be picked up later on by Rust.
You can find it here: https://twitter.com/tmccrmck/status/850465514807631872
1: http://www.vala-project.org/doc/vala/Overview.html#Memory_ma...
Vala's wiki has a page all about its ownership mechanism: https://wiki.gnome.org/Projects/Vala/Ownership
Thanks for the link, I'll dig in!
Case in point, in the Ion shell that I am working on, I have a shell expansion module within that takes a structure containing closures that are used to grab and expand array variables, string variables, tilde variables, and process expansions.
Because of this, the module can live as an entirely separate piece of software with no dependency on the specifics of Ion, and I can write reduced unit tests that extensively test every component. I could strip it out of Ion and use it with a completely different shell project. That shell would just have to provide the batteries (closures) that define what to do with certain tokens.
Yes, Apple's weight helped Swift tremendously. But people are switching from Obj-C to Swift because they think it's better, not because Apple's making them.
> How much of Swift is written out of Apple ecosystem? Not much.
Not much yet, because the Linux support is still missing a lot, but it's being filled in quickly, and a lot of people seem to be excited about being able to start using Swift on Linux.
That's the whole reason to have a new language; if we just wanted a language that works like elcurrent popular languages, we wouldn't need a new language for that.
> I have the feeling that Rust is destined for a niche like Haskell - cool, but not mainstream.
Becoming a Haskell or a Lisp -- never an industrially dominant language but one from which later and more industrially popular languages are continuously mining for some subset of their ideas because they solve problems long before most users of industrially popular languages even recognized the problem, much less the value of the particular solution, is not a failure.
And it makes the language one that is valuable to learn even if you don't often get to use it anger.
Rust is productive, efficient and a joy to use. For things we required C/C++ before, we will be using Rust now.
Sure, you've got to know about Option and Result to do anything with Rust, but you've got to know about exceptions and null pointers in the rest of those languages. But you can write coherent Rust using for loops everywhere instead of stupid closure tricks.
This. I've spent some time researching Rust but it's just ugly language for me, really. When I read D everything is so clean, so readable. When I need to read/write Rust, it's just not enjoying for me. There are people that will enjoy it for sure, that came for example from Haskell.
Create language like Rust with D like syntax, no GC, no additional indirection, RAII, less strict rules or possibility to turn off some of them and I am sold.
I think that language will end up looking a lot more like Rust than you think. You can't just compare a language that wasn't designed for zero-cost memory safety to one that was, as if the zero-cost memory safety features have no learning curve.
I disagree, I think you could pick different way based on Cyclone coupling with some modern static analysis techniques, it would not have so many features as Rust have but it would be less invasive and more "C/D'ish".
This I think is where a lot of the pushback comes from. People say want a new C replacement, but some portion of them look at what that actually means, and find they actually want C with a new feature or two because actually learning something new, even new looking, is work. Rust is not C or C++ with a few new features, it's a new language, and some people really want that, and some people are put off by how different it is.
It's perfectly fine for people to have a preference, but people should be aware of what it is - a preference. It's not some inviolate law that C's syntax and way of doing things is good, and we should make sure a successor follows that. It's importance to distinguish the familiar from the good. C has a huge advantage in that it's familiar, through it's own popularity and history and in those languages that it influenced. That doesn't mean it's inherently better though.
The borrow checker was brilliant. Any future language that doesn't use garbage collection will have to have one. That was the big advance in Rust.
But the object system (yeah, they're called structures and traits) is too weird. The enum approach to variant records is too weird. The error handling is too weird. The half-functional style is too weird. The widespread use of closures for simple stuff is too weird. There's too much unnecessary originality. Trying to port something from another language to Rust is difficult because Rust's ways of doing things are so different from other languages.
I have the feeling that Rust is destined for a niche like Haskell - cool, but not mainstream. That's sad. I really thought early on that Rust meant the end of the decades of hell from the lack of memory safety in C and C++. But that's not happening.
The Go crowd knew when to stop. The Rust crowd didn't.