From the article: "...fourteen companies responded to our outreach"
That's not a survey, that's a focus group.
I'm impressed with Rust. The borrow checker is the biggest advance in memory safety since garbage collection.
But there's a lot about Rust that's unnecessarily weird.
- The type system is unusual, and complex. It's hard to do anything without templates.
- The template libraries are heavily biased towards closure-oriented functional programming. This is cool, but hard to read. Parts of expressions are nameless and have no visible type. This is terse but hard to maintain.
- The hacks needed to avoid the need for exceptions are uglier than exceptions.
This creates major obstacles to adoption. Unlike the borrow checker, none of these things are clearly improvements. They're just different.
I would have gone for Python-type exceptions rather than error enums and macros, single-inheritance OOP rather than traits, and Python-type with clauses rather than RAII.
> - The type system is unusual, and complex. It's hard to do anything without templates.
You need generics to have the borrow checker that you praise above. Otherwise references would be basically crippled.
> The template libraries are heavily biased towards closure-oriented functional programming.
No, they aren't. I use for loops all the time in Rust, and it's totally natural to do so. My general guideline in my own code (which is followed in projects like WebRender and Servo layout) is that if it's one line, I use the functional idiom; if it's more than one line, I use a for loop.
> Parts of expressions are nameless and have no visible type. This is terse but hard to maintain.
Are you thinking of unboxed closures here? If so, they're necessary to avoid unnecessary heap allocations in normal code. If we didn't have that, then we couldn't compete with C++11 closures.
> - The hacks needed to avoid the need for exceptions are uglier than exceptions.
Unwinding is the single most divisive issue in Rust. We would alienate most of our community (I'm not kidding) if libraries required it. For a time, there was a language fork over the issue until it was resolved.
> Unlike the borrow checker, none of these things are clearly improvements. They're just different.
No, they're needed for the borrow checker and most of the other features to work.
> I would have gone for Python-type exceptions rather than error enums and macros
Then, as above, you instantly alienate all embedded developers.
> single-inheritance OOP rather than traits
This doesn't work in practice unless you introduce interfaces: it's way too inexpressive. This is why Java and C# have interfaces. Traits are just interfaces.
> Python-type with clauses rather than RAII.
You want to force an extra layer of indentation for every Box or Vec you create, and leak memory if you forget to write "with"? No thank you!
>> The template libraries are heavily biased towards closure-oriented functional programming.
> No, they aren't.
I think that came off more combative than you meant it to :)
>> Parts of expressions are nameless and have no visible type.
>Are you thinking of unboxed closures here?
I think Animats is complaining about the functional style in general, which is at least more common in Rust than in C++ or Python. And if that's right, it's a complaint I mostly agree with. Functional programming can cram quite a bit into one line, and that's all well and good, but a lot of the savings comes from omitting the variable names that are usually there in imperative code, and some clarity gets lost when you don't name things.
I agree with your rule of thumb. Functional stuff is great when what it's doing is obvious.
> a lot of the savings comes from omitting the variable names that are usually there in imperative code, and some clarity gets lost when you don't name things.
Are you talking about point-free style? Point-free style is rare in Rust, partially by convention, partially due to the fact that references and values are different and often you need to wrap in a closure to convert one to the other.
RAII (i.e. destructors that automatically run on values when leaving a scope) is what lets one avoid manually freeing Box and Vec, so you want to split the world into "destructor resources" (RAII) and "with resources", maybe with some guidelines for when to choose which.
Presumably a data type that contains a "with resource" would itself have to be a "with resource" (or else why bother?). This makes "with"-ness abstraction-piercing and infectious, so, for backwards-compatibility, if there was any chance of a type eventually containing a "with resource", the programmer would have to mark the type as such a resource preemptively, or else be forced to make a breaking change to their library. All for a minor syntactic difference in how one constructs values.
Also, how does one return values from `with` clauses? It's useful to be able to write a function that returns a file, so one would need yet more machinery, just to be able to cancel the normal with-cleanup. Again, all for a relatively minor syntactic distinction.
On languages that support currying, or leaving the last parameter out of the call if it is a lambda, you can create high-order-functions for resource management.
The effort is no different than implementing RAII classes.
withDBConnection {
r = db.execute ("SELECT....")
}
Here the function withDBConnection calls the lambda, providing it an implicit parameter for the db connection, which it fully controls.
There are more fancy ways to improve on this, specially if the language also allows for macros.
So I find a bit sad, that the discussions of with/using/try don't focus on the FP way of doing resource management.
The wrapper function controlling the lifetime is exactly why that scheme has both of the problems I mentioned.
It all comes down to: how do you write a fn connection() -> DB? You can't (or, at least, it doesn't make sense to) return the parameter from withDBConnection: the whole point is the wrapper function does the clean-up when it is done, invalidating the connection object. The only way to pass the value along is to have `connection` also take a closure, i.e. manual continuation passing style/node.js's "closure-hell". This can, in some cases, be addressed with async/await style sugar, but there's still semantic (mainly around composition) and performance issues with it.
Anyways, going back to the data type: if one has a type Foo with a constructor like fn create() -> Foo, and Foo is updated to contain a database connection, that constructor no longer works, and needs to also be converted to use CPS. That is, the interface of the Foo object has be infected by "private" details, meaning needs to to preemptively decide whether there is any chance of each datatype needing to hold one of these CPS objects.
(Of course, the code you suggest works fine in Rust now, and is even used for certain special cases, but that is very different to baking it into the language as a whole new syntactic construct.)
Python uses destructors/finalizers to free OS resources and a GC to free memory. Because Rust has no GC, it uses destructors for memory and OS resources. So adopting Python's scheme for calling destructors would force us into using that for memory as well.
His comment still stands. RAII is used in Rust for a lot of things, the least of which being managing files. RAII works as an interpoerable, implicit solution for both (unlike Python, where a lot of the use cases get magicked by the GC). with needs to be explicit (or else you leak), and is just more cumbersome to use, given how many things depend on it in Rust. In python you only use with for a few types, so the verbosity (and the fd leaks if you forget it) are no big deal. Rust has no GC, and it uses it for a lot more.
> The type system is unusual, and complex. It's hard to do anything without templates.
You mean generics not templates?
Nothing forces you to use generics, you can pass traits by pointer and avoid the code generation. It's actually a really nice property that lets you determine static vs dynamic dispatch independent of implementation
WRT Exceptions, this has been widely debated. I personally prefer error codes. I hate unchecked exceptions, ruins the whole idea.
Also I'm not a huge fan of OOP hierarchies. They tend lean more towards Is-A vs Has-A when the latter is more common across the domains I've worked in.
However, in the total 4 hours or so that I have been programming in rust the way of handling errors seems very sensible.
- Match expressions instead of "if (error) dosomething()"
- Match expressions with destructuring gives the error message.
- Match expressions are exhaustive which forces errors to be checked.
Its simple and elegant. I think the documentation is a little overwhelming when it comes to error checking and introduces non-essential craziness without warning.
> introduces non-essential craziness without warning.
Like what? (Disclaimer: I originally wrote that chapter as a blog post[1], so I'm curious to hear what I could have cut out. I tried to find things to cut, but given my target audience, I couldn't find anything substantial.)
First, thanks for taking time to write essential blog posts/documentation.
Here would be my suggestions, I hope they are useful:
1. Its about error handling. Options are conceptually similar but they're not error handling. Options can go somewhere else, maybe a previous chapter.
2. From what I understand from my almost zero experience of rust, the match/deconstruct with a Result type will do 99% of error handling -- even though it might be verbose or ugly to some. Its also the basis for the combinators. This can be the short and simple error handling chapter.
3. The combinators are nice constructs to make code more linear looking and are convenient when working with the std library. If they are stated as an optional convenience then they become less scary. Error handling is so fundamental that I should not have to learn about combinators to do it.
What I suggest is a rearrangement of the material. The documentation itself is clear -- its just too much.
Interesting. I couldn't disagree more! Combinators are very handy for error handling in Rust. More than that, combinators (and their limits) are essential to understand in order to motivate `try!`. Indeed, in Rust, matching against a Result type explicitly isn't especially common. Instead, one uses `try!`. `try!` abstracts three things at once: case analysis, error conversion and control flow. The chapter actually goes into detail and demonstrates why combinators and match expressions are insufficient for ergonomic error handling. IMO, this approach leads to the greatest understanding, because everything is laid bare and follows a nice progression from first principles.
With that said, in a book, I totally agree that the chapter could be broken up into more manageable pieces. But I do think all of the content there is essential for understanding how to do error handling in Rust. I feel pretty safe in saying that I use all of those concepts in most crates I've published.
Thank you for replying by the way. I'll continue to noodle on your thoughts and see if there's a happy place between us. (I kind of suspect that happy place is the new book, with content delivered in more manageable chunks. Alas, my blog is not book. :P)
"try!" has a built-in, hidden function return. It's only useful if the appropriate error action is to immediately bail out of the function. It implies that your program should be structured as functions which either succeed or fail, and that you don't need to log or display errors where they occur.
Hence the reason I said that it abstracts control flow.
> It's only useful if the appropriate error action is to immediately bail out of the function.
Which is exceptionally common.
> It implies that your program should be structured as functions which either succeed or fail, and that you don't need to log or display errors where they occur.
None of this is true, and I do log errors when they occur in Rust programs. (See ripgrep.)
`try!` is a convenience. Since errors are plain old values like anything else, you don't actually need to use it if it doesn't fit.
I wonder if you'd like the error handling chapter in the new book [1]; it started from a base of burntsushi's blog post but we did rearrange quite a bit. Option is in another chapter now.
The next iteration of the book will have a much more cut-down version of this; this chapter is _so_ thorough, but also gives you the impression that it's more complex than it has to be due to said thouroughness.
Rust now has not just "panic", but "panic::recover", with unwinding. That's an exception system. It's just one with weak syntax and semantics. Go went down this route, too. In the beginning, Go errors were fatal. Then the Go crowd added "recover". With unwinding. Both languages now have the heavy machinery for exceptions without full language support for them. The discussions of this on Rust mailing lists show much unhappiness with the situation.
If you don't put in exceptions, you seem to end up re-inventing "longjmp".
Panic recovery and panics can be disabled, and often are. Libraries are not supposed to rely on their existence. Thus these are not a form of exceptions ; they can't be used as such.
Their purpose is to recover from panics when embedding Rust in code written in other languages, where you don't want the panic to try and cross the ffi boundary (or to stop applications that should never crash from crashing at the toplevel, or for better reporting before a crash). Many FFI-based applications turn panics into aborts anyway. Firefox does.
The unwind-recovery functionality in Rust is just enough to get you these useful features if you need them (zero cost if you don't; turn them into aborts), without needing a full on exception system. The reason Rust avoids an exception system isn't the cost of unwinding, it's because it considers exceptions to be an inferior pattern compared to return value based / monadic error handling.
Rust doesn't have a mailing list anymore. I haven't seen much annoyance with panic recovery. There used to be some with panics in general because in the past they couldn't be turned off or recovered from without a new thread, but this has been addressed for ages with panic=abort and recovery. What the hell are you talking about?
Panics can be turned into aborts, and a lot of Rust apps do this. It is therefore poor practice, and unfriendly to your users, to publish a library that relies on unwinding. No popular library I know of relies on it.
Regardless of how valid or important the criticisms you list are (and there are already several replies tearing them apart point-by-point), I want to say I really appreciate hearing about potential pitfalls in new (and yes, hyped) technologies. If it turns out that none of these criticisms are that important, then that just makes Rust stronger. If they are important, the sooner we find out about them the better (and the Rust team can start addressing them).
> - The type system is unusual, and complex. It's hard to do anything without templates.
Do you mean the generic types and functions that stop you having to reimplement a whole pile of common patterns and data types yourself? You're welcome to write your own VecInt and VecString and VecMyStruct and VecYourStruct and OptionInt and HashMapIntString, but I'll go out on a limb and say that would be more annoying than <>'s in a few places. For one, not having generics would make it far more annoying to create reusable zero-cost abstractions around `unsafe` code, resulting in your other favourite criticism: more unsafe code for "unnecessary" (i.e. performance) reasons.
> - The template libraries are heavily biased towards closure-oriented functional programming. This is cool, but hard to read. Parts of expressions are nameless and have no visible type. This is terse but hard to maintain.
This isn't true: most generic types go via other schemes, and only use closures for simple combinators (making one line manipulations simpler and easier to read, but like with anything, can be abused), or when they're required to achieve the desired semantics (iterator pipelines).
---
In any case, (some of) your proposed replacements don't make sense as the only built-in for their given functionality, for a systems language. If the task in question can take the loss of control, then Python (or similar) seems like a better choice for you.
> would have gone for Python-type exceptions rather than error enums and macros,
True Python-style exceptions require allocations to handle errors.
> single-inheritance OOP rather than traits
This forces dynamic dispatch everywhere and loses type safety (when passing types through functions and storing them in data structures), rather than being able to specialise functions and data types, and track exact types through code.
Also, only offering single-inheritance has significant semantic problems, e.g. what happens when your type is both equatable and cloneable?
> Python-type with clauses rather than RAII
What exactly does this address? It seems just like a syntactic rephrasing of `let x = foo()`, given it would have to be compulsory for certain types, or else guarantees are lost.
---
Rust is, as you say, different to other languages, but very little of it (and none of the features you call out) was chosen just to be gratuitously different. The new features of course might just take a little time to get used to if one is coming from mainstream imperative/OOP languages.
Rust's generics have significant limitations, especially when mixing types, and are cumbersome to use.
For example try writing even a basic function accepting two scalars of type T and U and performing arithmetic on them. The way the Rust compiler works it isn't possible without specifying various bounds on the traits (which I appreciate, actually, for explicitness). And even then there are limitations around casting that require non-trivial workarounds (it is ridiculous, frankly, that the num crate isn't in the standard library, but that crate has its own problems).
And there is no specialization, which is a severe limitation.
This isn't wrong, but it seems to be putting the cart before the horse: even with limitations, having generics is still an improvement over not having them, in terms of one's ability to build type-safe abstractions (i.e. Rust would be a far weaker language without them). For instance, one can write a complicated lock-free data structure that works with any type T, doing the effort to optimise the atomic orderings and avoid leaks (etc.), all without forcing dynamic type casts or unnecessary allocations on downstream users.
> The way the Rust compiler works it isn't possible without specifying various bounds on the traits.
This is an explicit choice: C++-style check-at-use-site results in some seriously ridiculous error messages, and there's no boundary between implementation and signature, meaning a change to some small detail (and even syntax, in C++) in the implementation can accidentally cause downstream code to fail to compile. It is definitely annoying some times, but Rust, and most other languages with some sort of templates/generics, thinks the benefits outweigh the cost.
> And there is no specialization, which is a severe limitation.
> The way the Rust compiler works it isn't possible without specifying various bounds on the traits.
That's a feature! It works the same way in every language other than C++ or D that implements generics.
I much prefer explicitly typed generics to untyped generics as in C++. Not only are the error messages far more comprehensible, but it lets us avoid having to introduce confusing name resolution rules like ADL.
> And there is no specialization, which is a severe limitation.
Well "the same way" is pretty broad. Generics in C++ are too loose and I edited my comment to reflect that I appreciate the explicitness required. That doesn't mean it couldn't be revisited for usability.
Also, huzzah for specialization. I'll try that out asap. Thanks for the heads up.
The num crate is an open problem. There's a lot of contention around what it is supposed to support. Mathematicians, scientists, HPC programmers, prototyping programmers, embedded system programmers, pointer-arithmetic-doing programmers... they all want something different from a number crate. That's why it is not in the std library: there's no agreement on what it should do, and iterating toward perfection in the standard library will introduce a huge amount of churn and breakage. (And if you try to avoid churn and breakage, you're hamstrung by the current implementation to a num crate that suits nobodies needs.)
That's always hard. I was involved in the arguments over this for Go. Most languages today have multidimensional array support inferior to FORTRAN. This is not only embarrassing, it's why some heavy number-crunching is still done in FORTRAN.
Some people want nice, simple, dense multidimensional arrays. Some want the ability to extract a square section from an array as a subarray. Some want that for N dimensions. Some don't want the overhead for that generality slowing down their matrix multiply. Some are content with arrays of arrays. Some want ragged arrays (rows not the same length). Some want arrays in a format a GPU can use.
Passing the buck to each package, though, results in recopying arrays as matrices move from a function in one package to a function in another. One reason people use MATLAB, FORTRAN, and Python's NumPy is that they at least have a standard representation and matrix functions interoperate. I'd argue for supporting dense arrays in the language (and the optimizer) and doing the fancy stuff in packages.
Though if you want to be polymorphic over "adding things" you can just bound on Add :) If you want to be more restrictive over what you want to bound over then the Num crate comes into play.
You make an excellent point. I fall into more than one of the buckets you list, and it's definitely the case that there is unlikely a "one size fits all" solution here. However I just have this sense that there is an intersection of basic functionality for using "number types" that neither Rust nor the num crate really supports terribly well. Maybe I'll change my mind as I use it to build out some projects.
> I would have gone for Python-type exceptions rather than error enums and macros, single-inheritance OOP rather than traits, and Python-type with clauses rather than RAII.
Personally, I find error enums with macros, traits over traditional OOP and RAII to be strictly positive features, and all three are among the (quite numerous) reasons that I love Rust
You speak as if you're so sure of yourself. Not with opinions but apparently presenting facts. Problem is most of your "facts" are wrong. Thankfully the actual experts are patient enough to explain this. But not a single "Oh, sorry, I was wrong about that." Just silence on the things you were called out on, as if you weren't just called out on them.
My real criticism here is that Rust isn't able to wipe C and C++ off the face of the earth. Rust solves the three big problems which C fails to address - "How big is it", "Who owns it", and "Who locks it". We need that, with the endless CERT advisories of buffer overflows. That's the real issue here - memory safety is a big, big security issue. The threat is getting worse, too. It's nation states now, not script kiddies.
Rust usage is very low. It isn't even in the TIOBE Top 20 for 2016.[1] (In the detailed listings, it's at position 46, just above Awk.) Why is market penetration so low? I'm arguing that there's too much gratuitous new stuff for Rust to be acceptable to the C/C++ community.
I agree that I want Rust usage to grow. I disagree that we made technical mistakes that hamper its growth. You've thrown out dozens of reasons over time, none of which were legitimate. Every time you name some feature of Rust you claim is a "gratuitous new thing", we calmly show you why (a) that feature is already in C++ or (more often) (b) that feature is necessary to make lifetimes/ownership/borrowing work.
I think that the simple fact is that ownership, lifetimes, and borrowing are unfamiliar. They have a learning curve, period. We can't expect a language with them to take over the world in a year. Programming languages are a mature market. Progress will be slow.
Rust is doing far better than 95% of new programming languages after a year. Most programming languages are dead after a year.
Thank you for your impeccable responses, it's one of the reasons I love hn.
FWIW, I think a few explainer pieces would help bring about more enlightened discussions. For example, there is a lot of bikeshedding around Rusts' decision to wrap overflows and potential future improvements. But the rationale and interesting technical challenges each alternative brings is scattered across blog posts, forum threads, and Github issue tickets.
I'm using it commercially, though wasn't asked in the survey. I've had zero crash causing failures in production with a web interfaced analytics pipeline service I wrote. It just sits there, with its 10's of gigabytes of data running day in day out... handling millions of events every day, crunching business vital numbers. Best thing ever.
Similar story here. Been using Rust in production for only a couple of weeks for a financial analytics pipeline processing billions of events per day and TBs of data, but absolutely no issues so far. The predictable performance of Rust has been wonderful to observe, compared to GC'ed languages this iteration of the pipeline has replaced (JVM, Go). The type system makes otherwise daring refactorings a joy.
If you are; sorry you weren't included in the survey. I don't think everyone on the friends page was included, it was more of "everyone we know who uses Rust and we have an easy way of contacting over email"
I think it would also be useful to survey people who looked into using Rust but decided against using it, as responses from people who suffered through the warts is analogous to looking at the damage to bombers that made it back to base. Still relevant, but not the whole picture.
I'd expect to see mobile/cross-compilation (which I know is getting better) to rank pretty high among people who didn't stick with it, but maybe I'm projecting.
What I would be interested in is how these companies use Rust in their stack and what kind of problems they can solve. I suppose not every company can divulge this information but it would be awesome to know where Rust fits in for them.
> We received a resounding response to continue investing in building strong IDE tools. IDE tools in a commercial setting help teams coordinate their efforts by making it easier to navigate unfamiliar code, on-board new users, and streamline the development process.
I cannot wait. I cannot wait until this happens. Rust will become my main language.
This is one of the key things preventing us from moving to Rust. Yeah, I love vim but on a very large codebase an IDE just helps you keep everything much more organized and productive.
Moreover, moving to Rust would require investing in ports of some large-ish C++ libraries we use.
I think Rust is here to stay. It's a great language without a runtime that should be able to match C++ in performance when the compiler frontends catch up.
We'll start using it in some new projects but I doubt we'll port critical infrastructure for a few years.
Totally. Pattern matching and blocks as expressions are something I reach for constantly in C++ & Java and always end up disappointed when I remember they aren't there.
For me (also ~1000 lines of rust in production), the tooling and packaging was a huge winner. Having docs (with a doc generator), a test framework, a package manager, and a helpful [cross] compiler all put together, along with blazing speed and amazing reliability at runtime is an amazing combo.
I specifically like the immutable-by-default, borrow, and move semantics. I can write safe code without bloating up my source with 'const &', std::move, '&& ...' and the like.
The safety issue for me is about the least important thing. Touting it as the big reason to use Rust is a distraction from my point of view.
> The safety issue for me is about the least important thing.
For real. Rust is a great language, but for the love of god, all you hear is safety safety safety. I give about 2 out of 10 fs about safety. The worst part of C++? Dependency management. Cargo is sick -- hype that up more!
The worst part is people using it as if it was C with a C++ compiler.
I lost count the amount of times I had to create a nice C++ wrapper for "C++" libraries that are just a pile of C functions and data structures, due to copy-paste compatibility.
And yes, the respective lack of safety by not making use of C++ improved type system and standard library.
I don't know about 'main goal' but rust is fun to write. Far more than c++ for me.
Compile times. Traits. Generics that aren't templates. A package manager. Functional constructs. Cross platform. It's got all the shiny toys.
The state of the art in C++ is what? One file drop in headers, no package management and STL hell? Have you read the UE4 engine code base? What are horrific mess.
There are certainly problems with rust (imo tooling and libraries), but I don't think for a moment how enjoyable to write it is, is one of them...
C++ is a bloated language and I hate writing code in it. I hate that the mix of C++14/11/?? You get is different is every project. I despise that my C++ code from QT, UE, win32 is virtually incompatible, and the header/code legacy divide (or not if you happen to be in a system that allows it).
I guess your milage may vary, but I've not talked to many people who didn't like programming in rust... Mostly just people who found it hard, or didn't know why they would bother vs. their current 'good enough' language (eg. C#).
People havent had enough time to write huge messy codebases in Rust. Irrespective of language, I suspect that people hate reading source code and would much prefer to write code rather than read code. Ten years from now, I am hoping that Rust becomes the defacto systems language, but I strongly suspect that people will have similarly bad things to say about large Rust codebases then as they do about C++ codebases now.
Having confidence and trust in your code, and having less surprise runtime bugs in your code would naturally lead to a more enjoyable coding experience, I'd imagine.
I find Rust to be more enjoyable to write in than any other programming language I've used. The reasons why aren't that interesting, because they're the same reasons everyone else has: algebraic data types, safety, control and tooling.
(I just finished implementing a parallel recursive directory iterator that respects gitignore files. No unsafe. No data races. No mutexes. Blazing fast. So I'm particularly over the moon at the moment. :P)
I can certainly appreciate the sentiment that one of the most valuable features a language can have is the ability to make your co-workers write better code :) And it's hard to compete with Rust in that sense. That is, if you're starting a brand new project.
But in other circumstances, another valuable feature would be to be able to make your co-workers' (and predecessors') existing code retroactively safer. The easiest way to do that is probably by enabling the llvm/gcc sanitizers when compiling. But unfortunately that is often not a practical solution for several reasons[1], not least of which is the performance penalty.
So the more practical solution may be to use SaferCPlusPlus to replace (in a straight-forward but not quite automated manner) all the potentially dangerous C/C++ data types (pointers, arrays, etc.) with safe, fast compatible direct substitutes. Thereby salvaging existing code bases.
Wrt preventing the use of C/C++'s unsafe data types in new code, currently SaferCPlusPlus does not provide any mechanism for such enforcement. But you could imagine the effort required to implement such an enforcement mechanism might be small compared to rewriting some large code bases in, say, Rust. And in the mean time, even existing C++ static analyzers seem to be getting better at flagging potentially unsafe code.
The main reason I only have fun using C++ on private projects, while using JVM and .NET languages at work is exactly the way most people use the language on the enterprise.
The majority of C++ programmers I meet at enterprise projects don't even know what is CppCon.
Wasn't it? I can contrast the respective compiler error messages to point out that, whether it was designed as such or not, writing Rust IS more enjoyable.
This wasn't one of the question of the survey, but while having the right crowd around, I have to ask.
When will Rust get
1. function head patterns like other languages in the ML family, although Rust isn't really part of the family, but rather a distant cousin from another continent, which once played with ML and family during a summer vacation
2. support for naturally writing recursive functions
> Rust isn't really part of the family, but rather a distant cousin from another continent, which once played with ML and family during a summer vacation
It's actually more of a sibling in the family who ran away from home at the age of 6 and fell in with the crowd on the wrong side of the tracks.
Initially Rust was very much like ocaml. It isn't anymore :) Many of the normally-in-functional-languages features in Rust come from these days. Others were lost and re-added later. It's a very complex history.
> function head patterns like other languages in the ML family
could you elaborate? I'm not familiar with this feature (only have dabbled in sml).
> support for naturally writing recursive functions
> It's actually more of a sibling in the family who ran away from home at the age of 6 and fell in with the crowd on the wrong side of the tracks.
> Initially Rust was very much like ocaml. It isn't anymore :) Many of the normally-in-functional-languages features in Rust come from these days. Others were lost and re-added later. It's a very complex history.
Yeah, having tried Rust in those days, I kinda stopped when it broke every week, and was then surprised with the surface of 1.0. It seemed like a different person to talk to.
I've made my peace with the C'ification of Rust as the price to pay for attracting a large crowd of developers who grew up with C, C++, C#, Java, JavaScript, Ruby, Python, the list goes on. It's a reasonable sacrifice to make, but the two mentioned basic features aren't complex things to wish for.
> could you elaborate? I'm not familiar with this feature (only have dabbled in sml).
Imagine being able to hoist your match clauses into function head (signature?).
Not all languages with support for that force you to repeat the function name, and there are good arguments for/against. For example in Erlang, when you define an anonymous function, you do not repeat it:
OldEnough = fun(21) -> true;
(_) -> false
end,
Now, this may seem like a stupid little feature, but trust me when I say it's a natural feature to use like recursive functions after you're used to it.
Oh, yeah, I see what you mean by function head patterns. I'm aware of the coding pattern from Haskell, just didn't know the name :)
I don't think Rust will get support for that. You can simulate it with macros (and, later, syntax extensions). Of course, that isn't as clean as pure language support. I know why it makes recursion (esp tail recursion) easier to use though. You could always bring it up on the forums and try though.
> I just wanted to show that this isn't only useful for recursive functions.
Oh, I know that it's useful; I've used it in Haskell often. And the `fn foo (x) { match x {}}` pattern isn't uncommon in Rust.
> If Rust is planned to get HKT then I don't see why I cannot get pattern matching in function heads when there's also guards as found in ML languages.
The HKT proposal logically extends existing associated types syntax so that you effectively have HKT. It's particularly elegant in that it's something folks (who are unaware of what HKT is) on learning about associated types expect associated types to support. I've had folks ask countless times as to why you can't `type Foo<T>` in an associated type.
It also opens up access to patterns that weren't possible in the past.
OTOH function heads would just be sugar for fn + match. It doesn't open up new possibilities, it just makes some patterns easier to type. And frankly, with blocks being expressions, it's not much easier to type. You have to provide the function signature somehow, and it's there. The match arms are also there in both the Rust and haskell versions. The only thing exclusive to the Rust version is that you need to explicitly say `match`. Meh.
Languages have a "complexity budget" -- spend too much of it and folks won't learn your language because it's too complex. Rust has spent a lot of it on the borrow checker. Adding random bits of syntax spends this budget, and you need a compelling reason to do so. This is why I'm quite fond of the current HKT proposal -- I'd always thought HKT in Rust was pie-in-the-sky, but the current proposal ends up with a very unsurprising and natural-looking syntax (and doesn't feel "new"), which makes me think that it has a good chance of succeeding.
If you can come up with a good proposal for function heads, who knows, it might happen! But it will have to be good; I don't think just proposing function heads without tons of justification and/or a syntax that fits in naturally will work.
I should add 3, Erlang's bit syntax, which would be a perfect and natural fit for the target of Rust code. It's a very natural DSL to parse or build (complex) bit streams. You don't necessarily have to add bit syntax comprehensions.
We are discouraged from writing recursive functions, which comes naturally when expressing many algorithms. Missing TCO is one cited reason to avoid it, so I gather we're not supposed to, generally speaking.
Support for guaranteeing TCE is possible and has been proposed, but there remain a few open questions, e.g. what to do about local variables with destructors (personally I'd statically forbid them from being in scope at the end of tail-call recursive functions).
I know, that's why I wrote "when", knowing it's been considered, but it doesn't appear to be missed by enough developers. I'm just spoiled having used those two features, and anytime someone cites the ML family as part of Rust's influence, it reminds me of these two basic, missing features.
Regarding semantics, without having thought about the Rust semantics too much, I'd suggest to check out the most prominent uses for recursive functions in OCaml, SML or Haskell, then try to consider that as the sweet spot to support in Rust.
I don't think it's discouraged (a least, not in the sense of it being considered bad); I think they're just being explicit about what guarantees you have (or rather, don't have) for recursive functions
I’d love to see a survey talking about what bugs get actually solved by using one of these modern languages like Rust or Swift, since i personally never once in my career had a bug that wouldn’t have happened if i used const, let alone any of the many other annoying Rust features.
For a “systems programming language”, Rust doesn’t let you do anything i’d expect, like let you specify whether a signed integer is 2s compliment, and how it behaves when it wraps. Instead the programmer is at the whim of the compiler writer, same as in C, and its “undefined behaviour” which is a source of real, hard to track bugs. Rust doesn’t even have support for modern features like wide registers, or any features that help you deal with raw memory, it just marks it as “unsafe”. There aren’t even any basic meta programming features like struct introspection.
What problems does Rust actually solve that C and its copycats have been suffering from since the 70s.
> I’d love to see a survey talking about what bugs get actually solved by using one of these modern languages like Rust or Swift, since i personally never once in my career had a bug that wouldn’t have happened if i used const, let alone any of the many other annoying Rust features.
A quick analysis I did showed that half of the critical security vulnerabilities in Gecko would have been prevented by writing content and layout code in Rust. The other half were mostly JS issues, often having to do with built-in APIs that would also be safe if appropriately written in Rust (or self-hosted).
> For a “systems programming language”, Rust doesn’t let you do anything i’d expect, like let you specify whether a signed integer is 2s compliment, and how it behaves when it wraps.
Well, this definition of a systems programming language excludes C.
> Instead the programmer is at the whim of the compiler writer, same as in C, and its “undefined behaviour” which is a source of real, hard to track bugs.
> Rust doesn’t even have support for modern features like wide registers
What is a "wide register"? Do you mean SIMD? If so, it sure does [1].
> or any features that help you deal with raw memory, it just marks it as “unsafe”.
Sure it does. Rust helps you deal with raw memory by freeing you from the need to deal with raw memory in the vast majority of occasions. For example, in C you have to mess around with char pointers to operate on strings. In Rust you don't.
> There aren’t even any basic meta programming features like struct introspection.
Alright, thanks for clearing that up. I missed those in the documentation, since it doesn’t specify any of this directly.
Though, still wide registers aren’t a language feature. I don’t care, nor do i think any other seasoned programmer does, about the language “freeing me from the need to deal with raw memory”. It doesn’t. The types of problems you solve by introducing built-in string types and vectors aren’t problems in the first place. The problem is writing custom allocators for the data you have, and needing some basic bounds checking. Problem is working with structures of arrays instead of arrays of structures, because that can get messy and introduce basic typing bugs. I’d love to see any modern programming language at least make an attempt at making working with raw memory easier.
Clearly you haven't done even basic research into Rust. That's OK, but I don't think you should be spouting off in HN comments about how useless it is.
Rust is all about memory safety. The borrow checker gives you compile-time memory safety guarantees (sans unsafe blocks). The address of the struct you passed to some function which then took the address of an inner field and populated a value in a returned object? Yeah... Rust knows whether that's safe or you've set yourself up for a dangling pointer. Bounds-checking is child's play to Rust.
The bulk (if not the majority) of high-impact security vulnerabilities are related to memory safety in some form. Rust eliminates those as an entire class of problem.
I don't think this is a useful point, because by the same logic:
- Haskell doesn't eliminate memory safety issues, they happen when using unsafePerformIO (or Foreign.*Ptr, or inside the runtime, your choice),
- Python doesn't eliminate memory safety issues, they happen when using ctypes,
- JavaScript doesn't eliminate memory safety issues, they happen when calling into the C++ code of the browser,
- Java doesn't eliminate memory safety issues, they happen when using JNI.
All languages have some sort of escape hatch that can be used to drop to a lower level, in order to actually interface with the operating system (etc.), Rust is slightly unique in that it makes this escape hatch fairly explicit/prominent at the language level and also provides the power needed do that interfacing/implementation itself (additionally, one gets all the normal benefits of Rust inside `unsafe`, the feature just lets one do more, not change the semantics of existing code).
I never said i expect Rust to fix any of these problems, i would however expect it to at least try and introduce helpful constructs, instead of the ultimatum approach that every other language takes where you either program as expected or face the consequences.
It does provide a pile of helpful constructs (references, slices, generics, privacy), which, if they don't work 100%, are still brilliant building blocks for creating abstractions that expose safe interfaces, allowing the areas where "program as expected" is required to be concentrated into controlled areas/libraries. (And, all those features still work in the unsafe code, helping the programmer get it correct.)
It works really well in practice, even if it isn't bulletproof... there's nothing to stop a programmer typing 'unsafe' to side-step the safe interfaces in random places, but one quickly learns this is usually just heaping unnecessary debugger work on oneself, to track down little mistakes that the compiler would normally point to directly.
I’m not saying you’re bad. I’m saying there’s always a point where you want to go down to managing your own memory when performance is an issue, and no amount of language features is going to change that.
What i’d want is a generic support for preventing buffer overflows, or bad memory access where i need it. Writing my own growable data type using malloc and free is something i can do once, and i just have it, it’s not something that i have to worry about for more than 5 minutes at the beginning of a project.
> What i’d want is a generic support for preventing buffer overflows, or bad memory access where i need it
You have that.
For example, if you have contiguous memory, the various forms of the [T] slice type can be used, they don't impose any allocation expectations. (And, once the standard library allows pluggable allocators, you'll likely be able to use Vec directly, for a growable vector type.)
If you don't have contiguous memory, then it's a bit more work, but one can still use features like generics and privacy to spend a few lines building abstractions that bounds check in the right places. It's not like using a proof language to extract a program that is guaranteed to have no out of bounds accesses, or a compiler that some how deduces what the bounds of your data structure is, but the former has its own complexity downsides, and the latter seems infeasible, in general.
As I said, a slice can point to any memory, it doesn't matter where it came from: i.e. your allocator/things using it can expose slices instead of just plain pointers.
Generics and privacy are definitely language features, and they're core to the ease with which one can create abstractions. Rust is a systems language, it tries to give programmers the power to create their own safe abstractions as needed.
I wasn't talking about that. What i meant was, if i have to implement it myself, that's not a point in favor of Rust. Though the slice does help with rudimentary bounds checking, so that's a start, though i don't see how that differs from a generic accessor method or an overloaded operator.
A programming language can't anticipate every possible thing someone might want to do (or else every program would be one line: doMyThing()), the best it can do is provide tools for building the final product. Rust is pushing hard on making it easy for people to build safe, zero-cost abstractions that are reusable, via generics and especially cargo, so you often don't have to implement things yourself (or: don't have to implement things yourself twice), even if it isn't literally in the language.
> though i don't see how that differs from a generic accessor method or an overloaded operator
Err, it does all the bounds checking automatically? I thought you wanted these features without having to implement them yourself, but apparently writing accessor methods or an overloaded operator is OK too?!?
Why would it be safer to implement Vec as a "language feature" as opposed to implementing it in the library?
We actually used to implement Vec as a language feature, and it was a lot of work to write things like growth in raw LLVM IR (which is a miserable programming language) instead of just using a real programming language—Rust—to do it.
Not everyone has the privilege to be a single C coder owning the code without anyone else from different skill levels coding in it and without any additional use of third party libraries.
You've never hit a NPE or null pointer? Data races or use after free?
Const is nothing like references, not even close. I would suggest you spend a bit more time with the language to better understand exactly what Rust brings to the table.
One can get all of the problems mentioned with allocators other than malloc, up to and including using only the stack.
Also, sure the implementation of another allocator might use a smattering of `unsafe`, but I'm guess that it will be less than you probably expect: one of Rust's biggest is strengths is the ability to build safe interfaces for things without performance cost. One can see an example for this in http://os.phil-opp.com/ where the author is slowly describing creating an operating system, all while concentrating on how to exploit Rust features to let the compiler help the programmer, such as http://os.phil-opp.com/modifying-page-tables.html .
What does malloc have to do with anything? Returning a pointer to the stack is just as likely to break everything if done improperly. Moving or copying a struct that points at itself is also going to break.
In fact, "how to to either of these things" is such a common question for new rust programmers trying to solve their borrow checker complaints that it constitutes pretty good evidence people are doing these things all the time with little understanding of why they are problematic.
And even if you _do_ know better, circumventing the borrow checker is fairly trivial.
I published a regex library for Rust a few years ago. Since then, it's been downloaded a million times and used in over 300 published crates. I've had 0 reports of seg faults or buffer overflows.
Can regex libraries written in C say the same? I don't think so.[1]
I'm sorry, I have plenty of gripes about the Rust team touting safety as much as they do but your comment just misses the mark, in my opinion.
To begin with, "safety" isn't solved by just using const. It simply isn't feasible to write safe code in C without restricted feature usage to the point of being crippled. It's much easier in C++, but requires an incredible level of focus and "cruft constructs" like ' 'const &' and such. Arguably until move semantics were standardized with C++11 it was a challenge to write safe code.
I will agree that Rust's developers have perhaps come down too far on the side of "YOU ARE DOING SOMETHING DANGEROUS BY USING A POINTER." But I think you're exaggerating a bit about how hard it is.
Supposedly Swift 4 is introducing an optional borrow-checker. Types can opt-in and you get the same kind of lifetime guarantees and semantics as Rust. If you don't opt-in you get copying for value types and reference counts for reference types.
I'm curious how either approach will work out in the long run. I understand why Rust made the decisions it did wrt ditching built-in GC/non-GC support but it will definitely hamper its adoption for writing GUI applications. If Swift manages to thread the needle in supporting systems-level / kernel programming with borrow-checked types while not imposing that mental and maintenance cost on GUI apps it will be an amazing thing.
In any case it is exciting to see high-level languages with memory safety proving you can have something much better than C without going full-on virtual machine, runtime, and JIT.
> In any case it is exciting to see high-level languages with memory safety proving you can have something much better than C without going full-on virtual machine, runtime, and JIT.
"It" doesn't do anything. But some of its fans conflate "prevents memory safety bugs" with "panacea", at least by implication, as I perceive it.
Besides, right now a lot of the "memory safety" of Rust has much more to do with selection bias (it is used primarily by people who are explicitly interested in using its safety features). When one uses exclusively (or almost exclusively) the "memory safe" features of the language, of course things implemented in it will be free of "memory safety" bugs. Rust helps make doing unsafe things explicit. It doesn't prevent one from doing unsafe things.
Here's how I see Rust: it's far and away already superior than C++ for a certain class of systems application programming (e.g. performant desktop/native application code that sits at the systems boundary). Rust code that seriously competes with C or C++ for "low level" systems programming is going to have to make use of plenty of Rust's "unsafe" constructs. If it gains traction "in the wild," my prediction is we'll start seeing plenty of "memory safety" bugs in that area. The question then is how easily they're spotted and rectified.
> But some of its fans conflate "prevents memory safety bugs" with "panacea", at least by implication, as I perceive it.
While you may be right, I have never seen this happening (and I participate a lot in online discussions about Rust); people seem to be very careful about saying that Rust prevents memory safety bugs, not bugs in general. People do say that the type system _helps_ prevent general bugs, but that's only to the extent that any similar or more powerful type system (Haskell, xML, etc) can.
> Rust helps make doing unsafe things explicit. It doesn't prevent one from doing unsafe things.
Rust gives you the tools to prevent yourself from doing unsafe things (and still be able to write software).
You can get a lot of stuff done in safe Rust. When you need to drop down to unsafe Rust, you can design safe abstractions around the unsafe code, and manually verify the safety of the abstraction. This has a human component, so it's not perfect, but it's pretty close :)
The unsafe feature isn't just there for explicitness, it is designed to provide the ability to do this -- the ability to design safe abstractions.
> for "low level" systems programming is going to have to make use of plenty of Rust's "unsafe" constructs.
Designing an operating system low level enough? There are a bunch of initiatives to write an OS (both serious and as a learning project) in Rust. As far as I've seen, all of them avoid unsafe code like the plague and design safe abstractions around everything so that they only need a smattering of unsafe code. os.phil-opp.com has a bunch of blog posts on OS design in Rust, some of which explicitly demonstrate this pattern (e.g. the page table one).
When you say "When one uses exclusively (or almost exclusively) the "memory safe" features of the language", you're talking about basically the entire target audience of Rust, and this includes low level users. Rust isn't supposed to be used with lots of unsafe everywhere; and folks have tried hard to make it so that this isn't necessary even for low level applications (the Zinc project, while no longer maintained, is an example of this for embedded software). Of course there are areas where you'll be using more unsafe code than others, but you're still supposed to be "almost exclusively" using safe Rust code. If you need unsafe everywhere for your low level application then that's something you should bring up with the Rust community. (To be clear, the Rust ecosystem and language right now has some issues that crop up in low level development, but these are being worked on)
In fact, there is only one place where I've seen unsafe code being sprinkled around liberally, and that's when binding to a C/++ library. Currently my job involves a lot of this, and I'm trying to make it safer (largely succeeding!), but it's still has a higher density of unsafe code than, say, Phil's Rust OS (or I think Redox, but I haven't looked at that in a while). In this case, though, you're already talking to C/++ and there's inherent unsafety there (plus an impedance mismatch), so you can't always blame Rust for it :)
------
I guess you're right that "Rust prevents memory safety issues" isn't 100% accurate because `unsafe` exists. "Rust gives you the tools to prevent memory safety issues" might be more accurate, but still misses nuance. I think for a soundbyte on Rust it's accurate enough to say (because it prevents these issues more or less as much as possible without being useless for writing software), but it's a caveat that we should probably mention more.
That's not a survey, that's a focus group.
I'm impressed with Rust. The borrow checker is the biggest advance in memory safety since garbage collection.
But there's a lot about Rust that's unnecessarily weird.
- The type system is unusual, and complex. It's hard to do anything without templates.
- The template libraries are heavily biased towards closure-oriented functional programming. This is cool, but hard to read. Parts of expressions are nameless and have no visible type. This is terse but hard to maintain.
- The hacks needed to avoid the need for exceptions are uglier than exceptions.
This creates major obstacles to adoption. Unlike the borrow checker, none of these things are clearly improvements. They're just different.
I would have gone for Python-type exceptions rather than error enums and macros, single-inheritance OOP rather than traits, and Python-type with clauses rather than RAII.