In related news, note that Rust 1.4 was just released yesterday: http://blog.rust-lang.org/2015/10/29/Rust-1.4.html , which I figure is worth mentioning here as the announcement thread didn't get much traction on HN.
Definitely worth mentioning! I completely missed this release. A bit sad too, since it's the first release (since Rust started listing contributors) that I didn't contribute to :'(
What's the status of Rust these days? I can see it's hit 1.x, but in practice is it ready for production use? How comprehensive is the standard library, dependency management, etc.?
From a brief glance through the examples it looks pretty functional... is it basically a systems-ish scala?
It's pretty stable at this point (1.x means that no backwards compatibility will be lost as in the past), and it's already used in production (e.g. Skylight [0] is written in Rust, as far as I know, and don't forget about Servo).
Dependency management with Cargo is hands down one of the best out there, it just works. The standard library continues to expand, but there are also many other external libraries you can find on crates.io.
> it looks pretty functional... is it basically a systems-ish scala?
I think it's better to compare Rust to ML languages family (OCaml, F#, and so on), as it was clearly influenced by it. It has many features you can find in ML languages - pattern matching, ADTs (algebraic types that are enums in Rust), and many other similarities.
Overall, the language feels very robust now, and I enjoy it much - it's a very decent replacement for C++, basically "C/C++ done right".
Rust and Scala may be cousins due to their ML influence, but Rust itself isn't particularly influenced by Scala. In contrast, there are quite a few design decisions in Rust that were lifted directly from OCaml. Calling Rust "C++20 + OCaml" would be a decent categorical starting point.
(Caveat: the lead designer of Rust's type system, Niko Matsakis, has done a fair bit of work with Scala, so there may be subtle influences.)
Rust and Scala operate in entirely separate contexts, are intended for entirely separate domains, have entirely separate goals, and are intended for entirely different sets of programmers. Lessons that apply to Scala do not necessarily apply to Rust.
The cascade of efforts at error handling, which I think at one point involved a macro and a magic method name, rather than getting on with higher-kinded types to allow them to do it right.
Allowing "return".
Mandating braces for functions and control flow constructs.
Rust iteration, which is both more limited and less safe (laziness) than the scala/ruby style of passing a closure.
You must be referring to ancient Rust with its condition system and such. Modern Rust code just uses Result types.
> rather than getting on with higher-kinded types to allow
> them to do it right
Er, no, you don't need HKTs to "do it right", you'd just need HKTs to do it generically. Rust's error handling works fine for specific types; you can even write macros to emulate do-notation (and people have).
> Allowing "return"
Welcome to the realities of imperative programming. :) Unlike Scala, Rust does not aspire to functional godhood.
> Mandating braces for functions and control flow constructs
Scope in Rust is very, very important for expressing when references go out of scope and when resources are freed. Leaving scope implicit would be massively frustrating.
> the scala/ruby style of passing a closure
Rust had internal iterators for years, and the entire `for` construct was based around them. They were removed because they were found wanting.
As I've mentioned in a sibling comment, the lessons that Scala may have learned in its life do not necessarily apply to Rust. They are very, very different languages.
I am a Scala developer but I agree with your points. The only thing that just simply drives me mad in Rust is mandatory semicolons. Once you are used to be able to omit them then it gets really annoying to go back using them.
I actually agree with this one. :) The usual argument in their favor is that having to explicitly return `()` all the time would get tired in low-level C-alike functions that mostly operate via side effects, but I'm not particularly swayed that we should be optimizing for that use case (but then again I'm also of the opinion that bitwise operators don't deserve their own symbols, so I may already be an enemy of this crowd :P ).
Thank you! It's really hard to understand where criticism is from without specifics, even if you're not the OP.
I would love higher kinded types, but most of our users are asking for other type system features first. What we've done doesn't preclude a HKT style in the future, as the signatures are the same.
I'm not aware of an imperative language that doesn't allow early returns, maybe there are some, but I find 'guard clauses' to significantly combat rightward drift.
Braces are to appeal to our core audience, who have been using braces and semicolons for decades. Absolutely, 100% subjective though, I know people who prefer the whitespace-based syntax, but you really only get one or the other.
> Rust iteration, which is both more limited and less safe (laziness) than the scala/ruby style of passing a closure.
I'm not sure specifically what you mean here, around both safety and closures.
Here's Rust:
let v = vec![1, 2, 3];
let v1 = v.iter().map(|x| x + 1).collect()
Here's Ruby:
v = [1, 2, 3]
v1= v.map {|x| x + 1 }
Other than the laziness, both are "pass a closure." And I'm not sure how safety ties in here at all.
Most of these things seem like subjective preferences, rather than things that are "completely broken."
> I would love higher kinded types, but most of our users are asking for other type system features first. What we've done doesn't preclude a HKT style in the future, as the signatures are the same.
I think many users don't know that they want higher-kinded types, only that they want particular features.
> I'm not aware of an imperative language that doesn't allow early returns, maybe there are some, but I find 'guard clauses' to significantly combat rightward drift.
I don't know what you consider an "imperative language"; I'd consider Scala an example. I think a decent error mechanism (which Rust does have, even if the implementation is ad-hoc and reliant on a macro) provides a better way to express guard clauses. I think return is untenable in a language with lambdas, because any possible choice of semantics for return inside a lambda (including "compile error") will confuse a decent proportion of programmers.
> Braces are to appeal to our core audience, who have been using braces and semicolons for decades.
If by your core audience you mean C++ users then they're used to being able to omit braces on if/while/etc.
> Other than the laziness, both are "pass a closure."
I'm objecting to the .iter() part. Or perhaps more generally to the lack of something as extensible as do or for/yield. The for/yield construct turned out to be far more valuable to Scala than was realised initially.
> And I'm not sure how safety ties in here at all.
I meant unsafe in the colloquial sense. I think laziness makes programs very hard to reason about.
> Most of these things seem like subjective preferences, rather than things that are "completely broken."
I agree that they're not completely broken, but I think they're more than subjective; in many cases Scala has tried the various options over the years. We've tried exceptions and macros and found a better way of doing error handling. We've had return in the language and discovered it to be more trouble than it's worth. We've had a notoriously flexible syntax around function calls and seen a consensus on the best style develop over the years. We've seen some control-flow structures virtually wither away and others become indispensable.
> Or perhaps more generally to the lack of something as extensible as do or for/yield. The for/yield construct turned out to be far more valuable to Scala than was realised initially.
Yield doesn't make a whole lot of sense in a systems language with machine-level control over stacks and allocation. It doesn't make a whole lot of sense in a systems language where the idea of jumping out of a stack frame either means doing strange low-level stack jumping tricks (defeating standard CPU/compiler optimizations) or allocating behind your back (which is something that Rust never does). Very early Rust actually tried using yield for iteration and it didn't work.
> We've tried exceptions and macros and found a better way of doing error handling. We've had return in the language and discovered it to be more trouble than it's worth.
We've experimented with these too. And I use return all the time in Rust. It's really useful and I would hate to see it go away.
I think Rust and Scala are just different languages. One's an applications language built on the JVM and the other is a low-level systems language with manual memory management. That difference means that different decisions may be appropriate.
You seem to have missed my comment earlier about Rust blatantly lifting features from OCaml. Here, in the Rust reference, are about twenty other languages that Rust was inspired by: https://doc.rust-lang.org/stable/reference.html#appendix:-in... Being presented with the idea that Rust wasn't specifically inspired by Scala should not be taken personally. :P
Okay. I can assure you that I don't want to remain in the dark about things that are "completely broken", but I can't do anything to address them without specifics.
Well, that entirely depends. You made a really strong claim, and those are worth taking seriously. But that doesn't mean that a response might come out that way. For example, if someone claimed that using four spaces instead of two spaces for indentation is "completely broken", then "that's just your opinion" is a pretty acceptable response, in my opinion.
But, I can assure you that Rust is most certainly not perfect, and that we roughly try to prioritize work alongside what people ask for. Part of what I do is gather this kind of feedback, so that I can tell the rest of the team "Hey, lots of people have started asking about x. Maybe we should try to bump x up in the schedule." And we do do that, though obviously, everyone has a pet feature, and you can't satisfy everyone. (My pet feature that's far off: HKT) But I can't take action against "hey someone on HN says Scala has some secret sauce and we suck, but won't divulge the details."
It's not your job to help us improve Rust, but making really vague negative claims doesn't help anyone, really.
Hey did you know: the people you're talking to are real people, who work really hard on a thing that they really care about, and you are being a real jerk.
Oh cool, thanks for the summary. Maybe it's time I start to dabble... It'd be good to be able to deploy binaries without VMs/requiring a ton of dependencies to be preinstalled.
Rust doesn't require a VM or any prerequisites installed on a target system. In this regard it's comparable to Go (though I'm not sure that it's viable to statically compile everything into one binary, but technically it should be possible). Anyway, it compiles down to the machine code, so at most you'll need to have a bunch of libraries along with your executable file.
Hopefully you'll have a lot of fun with learning! :) Oh, and almost forgot: Rust community is one of the best that I've seen, so feel free to ask any questions on http://users.rust-lang.org - I'm sure there's a lot of people who'll be glad to help you.
To elaborate, by default Rust programs are entirely statically linked (dynamic linking is available with a compiler flag), though with a single dynamic dependency on libc. This means you can compile ordinary Rust code into a single executable and run that executable on any machine with a compatible version of libc (which is fairly universally available, so not a problem in practice).
If you want no dynamic dependencies whatsoever, then Rust supports linking to musl, which is an implementation of libc that's designed for static linking.
I believe this refers to the difference between pre 1.0 churn as the language was being initially designed, and post 1.0 stability. Before, existing code was frequently broken by new changes as the language evolved. Now you should generally expect to see any current code run on any future 1.x versions without issue, and probably in all future versions. If any new language constructs are added in future, they should typically be backwards compatible, only extending the old system, rather than making changes. It's possible at some point they will be compelled to break something to fix a serious flaw or issue. For Rust, I'd have to imagine this would be safety based. As language designers they will try hard not to, and it would be a major version change, such as 2.0.
Python has had a lot of challenges with their changes from 2.x to 3.x, even though the language seems clearly improved. Much of the problem is converting popular existing libraries, which rely on a lot of donated time and effort. The Rust community is hoping to keep from being in a similar situation if at all possible.
This is because the compiler wasn't checking some cases strictly enough, and you could "hide" constraints from the compiler, causing them to fail to be enforced.
They checked against crates.io when raising this RFC, and found that there were 35 crates (packages) that would be able to keep compiling if this turned into a future-compat warning, to become an error in the release afterwards.
Because `Option<T>` requires that `T` be `Sized`.
Poking around on 1.3, I think that it would be an error to instantiate this trait with a [i8] or something that was not sized anyways, so you're not losing much by needing to declare the trait properly.
To elaborate, Rust has a category of bugs called "soundness bugs" which are allowed to justify breaking backwards compatibility, though the team still endeavors to make breakage as minimal and painless as possible. The Rust developers have written a tool called Crater to gauge the impact of any potential change to the compiler, which is actually really neat: it compiles nightly versions of the compiler against every package on crates.io (the central third-party package repository) and generates a report of any packages that fail to compile due to changes to the compiler.
To answer the question of why they don't bump the major version when soundness fixes break backcompat, it's because soundness bugs generally have implications for memory safety/undefined behavior, and it's assumed that people using Rust would prefer unsound code to break rather than to continue being potentially unsafe. Also, thanks to Crater, the Rust developers generally take it upon themselves to file patches with third-party crates to update them such that the package developers themselves are (ideally) neither blindsided nor inconvenienced.
Neat! That's very good handling of "as intended". Being explicit about it, having a warning phase, then moving to "more correct" to match the intent, and missed edge cases.
An entire game, written mostly in C for a (comparatively) extremely primitive CPU with a similarly primitive compiler. Performs all rendering in software, with tightly optimized x86 assembly targeted towards said extremely out of date architecture, and was the absolute bleeding edge of what was possible when it was released. On top of that, DOS was the target, which meant that a fair part of the codebase is dedicated to what programmers today would think of as an operating system, from device drivers to interrupt handlers to memory managers.
This:
A level renderer with none of the game logic, written in a higher level language that, nonetheless, has access to a much, much more sophisticated compiler and target hardware than the original game had, that offloads most of the rendering to the GPU anyway, whose author was never once required to think about optimizing anything because any computer made in the last decade could render such scenes in its sleep a thousand times over without so much as a snore.
I'm not at all trying to poo-poo the author here, but there is no meaningful comparison you can draw from this.
I'm the author, no poo-poo-ing taken. You're completely right. The rendering code is shamelessly unoptimized indeed: doing frustum culling naively actually slows down the code because of the extra draw calls. I could probably get a boost in frame rate--by for instance memcpy-ing chunks into a dynamic buffer and keeping the single draw calls---although it already runs at 300 FPS on the integrated intel card of my laptop. So I'd rather add more features, testing and documentation than worry about perf just yet.
Thank you for not taking it that way. From-scratch recreations of games and renderers are always cool, and you're absolutely right to not obsess over the performance of a Doom level renderer in 2015. I just wanted to make it clear that this is not the right candidate for a "C vs. Rust" benchmark.
Not to mention that it's a completely different rendering algorithm that produces different scenes: this is a "true 3D" geometry converter, while the original Carmack code was a "2.5D" rasteriser. That's why original Doom couldn't look up or down and has only one "vanishing point", whereas you can clearly see non-parallel vertical features in the modern screenshots.
I don't see a great scope for auto-vectorisation etc there. You could write a new 2.5D renderer which assumed its availability, but that would be not quite directly comparable.
No perceptible performance hit on a modern machine? Absolutely. I'm sure you can run 100 copies of Doom simultaneously in Javascript or whatever.
No perceptible performance hit on a 486? Somehow, I don't think that the 486 is an especially important target for the LLVM or Rust developers (though such chips are used in embedded applications to this day).
But the real reason that the comparison makes no sense is that the bulk of the processing time (but not the bulk of the code, mind you) is spent in those tightly optimized assembly loops. Either you keep it as such and it's not really much of a battle between C and Rust any more, or you rewrite the critical sections in Rust and you'd lose as hard or more as if you wrote them in C.
That bug just looks like a glitch with ISA support in the generated code. It's trivially fixable I'm sure, and not really relevant to the subject of optimized assembly.
I wouldn't trust any of the benchmarks in the Benchmarks Game. I say this as someone who spent a fair while optimizing the Rust benchmarks to use all of the hacks the other languages use in some[1]. _Very_ few of the benchmarks are actually fair, and those that are normally don't matter.
You're generally better off with random internet tests than the Benchmarks Game, since they at least don't have the kind of cheating-but-not-cheating that ruins the Benchmarks Game.
That said, it's totally possible to do assembly-level tuning in Rust and, sans-SIMD, there's no real difficulty with getting Rust to go fast on these workflows.
[1] https://alioth.debian.org/tracker/?func=browse&group_id=1008... See ones with "Joshua Landau" in title. All (but thread_ring, which is literally the silliest benchmark I've ever seen) beat C++ (sometimes by a lot). The disclaimer would be that none of my submissions have been submitted (or rejected) yet.
He's saying that all the fastest benchmarks game programs in all languages cheat, relative to the slowest ones. :P This isn't an attack on your capacity as the site's maintainer, this is an entirely expected outcome due to the high-profile nature of the site. He's saying that random tests on the internet are "better" because they attract less attention and thus have less exposure. It's a problem with microbenchmarks in general.
Your defensiveness is unwarranted and your interpretation assumes bad faith.
> So programs that are compiled cheat, relative to programs
> that are interpreted?
Programming languages whose dominant implementations are interpreted generally (though don't necessarily need to) provide less direct control over hardware, memory layout, and algorithms, and in practice will defer to C via FFI when such control is necessary. Because the benchmarks game generally prohibits calling out to C, it means that many "tricks" employed by low-level languages are unavailable to the higher-level languages. Whether or not this is a desirable property is up to the reader's interpretation.
> Seems like "cheat" needs to be in scare quotes.
Indeed, because it's essentially impossible to enforce anything like "idiomatic" code in any of the benchmarks game languages, and so whether a program is "cheating" is up to the reader's interpretation. That said, the fact that it is up to the reader's interpretation means that it is valid for some readers to conclude that some programs are "cheating", while other programmers may differ. Personally, I think the point is moot, and that microbenchmarks aren't worth getting so worked up over. :P
It would be completely disingenuous to suggest that Haskell is on top for any reason other than it doing a different thing to the other benchmarks. Haskell is just not that fast. So, in a sense, it cheats.
Of course, I copied these techniques in my Rust implementation. So, if Haskell cheated, I cheated. The same can be said for almost all other benchmarks - but not normally quite to this extent.
But it's not cheating in a literal sense, since (AFAIK) the Haskell program has been accepted on the basis that it follows the rules. So it doesn't cheat in that it follows the rules, but it cheats in that it should be breaking the rules.
This reminds me I haven't written up stuff for chameneos-redux, which is probably the most fun and over-engineered of them all.
The fasta/Haskell thing I was mentioning are the parts at "The Haskell code does a quite clever alternative" and "the Haskell code just caches them all in an array".
The pre-compacting I mention in k_nucleotide was already done in the Scala implementation.
How parallelism is added also varies. The Scala code does each of the test cases in parallel whilst the Rust parallelizes each internally. (My new Rust code parallelizes even better.)
Several implementation make their own specialized hash table, and there isn't one particular hash table to implement. This is very important since hash table lookups take a substantial fraction of the time.
Note that I pick on Haskell and Scala here only because they have the best hacks, not because they're the only ones doing hacks. (I'd imagine listing all of the differences would take much longer than I have time for.)
thread-ring has lots of different implementations. Some are just scheduled coroutines restricted to a single process, then you have stuff like [1] which uses mutexes, real threads and sets thread affinity. I'm not totally sure what [2] is but I think it's something else entirely. I'm not being too specific here because honestly this benchmark confuses me.
If the Haskell fasta programs cheat, my fasta program cheats.
If the Haskell fasta programs do not cheat, my fasta program does not cheat.
These statements are made to the best of my knowledge, although someone with a definitive understanding of the rules (you, perhaps) is in a better position to make such decisions. I can make similar comments about my other submissions, referring in each case to different competitors.
I'll be unambiguous when you make the rules unambiguous.
I'm being ambiguous because you're forcing me to.
I've told you what I'm unsure about. I've linked to write-ups of what I've done. I've pointed to parts in other programs that seem not to clearly match against the rules.
Yet you, who knows the rules, have opted not to make one clarification about this. Not a single "yes, that technique is legal" or "no, this is against the rules". If you did this, I would happily give a clearer response. If my programs do break the rules, I would happily retract them until they don't, and point out which other submitted programs should also be retracted. This would not bother me.
Instead, you're demanding me to make definitive statements on information you're keeping hidden from me.
This is not making me feel pleasant, and this does bother me.
> You accuse programs of cheating and then refuse to say what you think they are doing wrong
Firstly, I didn't accuse them of cheating. "X-but-not-X", under my understanding of idiomatic English, refers to something that's X in spirit but not technically X, or vice versa. The former was what I was going for.
Secondly, I have said what I think they were doing wrong, at least for some of them. Since you seem to have missed it (it's literally the only chain in this subthread you haven't replied to), see [1].
To be doubly clear, for Haskell I am talking primarily about the fact that the Haskell code caches the result of the linear searches done on each RNG output. (Not the RNG output itself, but the transformation applied to the values produced.)
You do write that a linear or binary search must be made, and on more careful reading this probably does make the Haskell code illegal. (FWIW, that's more of a problem for the Haskell code than the Rust, which IIRC isn't so bottlenecked there - and technically Rust does do a linear search.)
The second point with regards to Haskell is the copying of slices from a buffer of doubled length. As far as I remember, no other implementation (bar mine) does this. But the rules say
> generate DNA sequences, by copying from a given sequence
so don't really give much guidance. I'd assume this one is legal, but I'd encourage you to ban it anyway or get all of the implementations that don't do this updated, because it's extremely unfair on those that don't.
Then, still for fasta, you've given no guidance on how the code is legally parallelizable. The Rust code (prior to mine) parallelizes reading input with adding line breaks. I'm fairly sure other languages do different things, but it's taking too long to remind myself what they do actually do. I, as I've stated, parallelize the RNG by implementing skip-ahead to allow working on separate blocks at the same time. Is that legal? It's certainly not explicitly banned.
For knucleotide, is Scala allowed to precompact the input?
What are the rules about the hash-table? You do give one ("grow the hashtable from a small default size"), but it's not clear what a small default size is. Some of the code seems to violate my intuition of small.
How about pre-lowercasing the input. Is that legal?
For thread-ring, the top Haskell competitor uses pseudo-preemptive threads, but they don't allow the compilers to make any premption points (if that makes sense). This means the runtime is actually unable to preempt at all. The internet tells me Haskell can only preempt at allocations, which aren't being made. Is that legal? (It doesn't help that implementations have moved from the original general-purpose hashes to generally-poorer specialized ones.)
For chameneos-redux, you say
> don't use arithmetic to complement the colour, use if-else or switch/case or pattern-match
Go does a lookup in an array with colname[complement[c0|c1<<2]]. Is that legal?
Most of chameneos-redux is underspecified. There are more differences between the C and C++ implementations than there are similarities. I'm running out of motivation to list them all, though, so I'll continue once the points above are clarified.
Note that these are only the things that are ambiguous with regards to the rules, not the ones that are merely unfair.
>>>Not a single "yes, that technique is legal" or "no, this is against the rules".<<
> The fasta description has stated "don't cache the random number sequence" since 2011.
Perhaps one should notice that this is one of the few hacks that people aren't doing.
>> Firstly, I didn't accuse them of cheating. "X-but-not-X", under my understanding of idiomatic English, refers to something that's X in spirit but not technically X, or vice versa. The former was what I was going for. <<
Given your stated understanding, you still accuse them of cheating in spirit.
In ordinary English you equivocate, to have your cake and eat it too.
I don't want to get into a semantic quibble, since it's really besides the point, but to quote your link:
> You can use cocoa powder to make the cake rather than chocolate - it's a bit of a cheat, but nobody notices the difference.
If you're trying to produce cheap cakes and your competition also uses cocoa powder, it's a mark of intelligence to follow along. It's still a bit of a cheat, though.
---
If you don't accept this reasoning, maybe I'm wrong and the word is unfairly used here. But please at least accept that I meant no insult, regardless of whether I have to vocabulary to express myself correctly.
It's not considered cheating for the Language Benchmarks Game, seeing as it's just a game, but it would probably be considered cheating for a benchmark attempting to compare performance for "realistic" programs.
"The performance of a benchmark, even if it is derived from a real program, may not help to predict the performance of similar programs that have different hot spots."
This sort of querulous point-sniping mainly persuades me that the criticisms are entirely valid. So if you're looking to make people think that the Benchmarks Game has some real-world utility, you might want to try a different approach.
I'll give you points for consistency. But enough relevant information is already before me. With tens of millions of websites, I have no obligation to look carefully at each one to decide it's not for me.
Yes, exactly. In the same way that if I see bad Yelp reviews and the business owner responds poorly, I think there's no need to go by for a sandwich just to be sure.
Wow Rust 20%+ faster than gcc / C in k-nucleotide and fasta. Then again the Rust vs C++ makes me wonder if this benchmark is flawed. 4.4 vs 38 seconds for binary-trees. Somethings not right with some of those tests.
Edit: Looks like an C++ optimization issue in that specific task. Broken down by task, C is at the top followed by Rust.[1] Rust is at or near the top in every category. Very impressive.
What you see is a couple of toy benchmark programs being "20%+ faster" than a couple of other toy benchmark programs, in a particular context [those language implementations, those command lines, that OS, that hardware…].
The Rust ecosystem is still building up a lot of the libraries to make game programming practical. A lot of this is happening under the auspices of the "Piston" organization on Github: https://github.com/PistonDevelopers/ . Piston itself is sort-of-a-game-engine, sort-of-a-collection-of-modular-game-related-libraries.
Meanwhile, Rust also has the Glium library for OpenGL programming, which is apparently so good that it makes OpenGL programmers froth at the mouth (I've never done OpenGL programming myself, but the praise is universal among all my graphics-programming friends).
I think you should check out Arcaders.rs.
There are still proposals for some sort of inheritance to be added, to make it easier to do the DOM, which might (or might not) be useful for an entity system.
Also, you'd likely want to be using Rust 1.4. It just came out this week.
Because Rust uses LLVM to optimize the gap between C++ should be small enough that architecting for memory allocation and cache locality should FAR outweigh the difference in language implementations.
If you look at things like Ogre and Box2D, they can likely be sped up by a factor of 10x in either language. (not total fps in ogre, just the engine part).
In general Rust is fast but slower then C: "Rust is safe indeed but, unfortunately, far from fast. By the moment of writing this article, it is comparable to Java, Go, and Haskell regarding performance" http://www.viva64.com/en/b/0324/
Please not this again. The article you mentioned is incorrect in many places, and was heavily criticized in various communities.[1][2] It doesn't qualify as a valid criticism. It is rather a badly written static analyzer promotion.
Also, microbenchmarks are usually not very good at representing the real world characteristics. But then again, please look at the current version of the linked benchmark.[3] Rust now positions itself in the second place, thanks to the recent improvements given to the Rust programs used in the benchmarks. I believe Rust isn't as fast as highly optimized C, but I think it is safe to say that Rust is just as fast as C++, and both Rust and C++ are much faster than GC'ed languages such as Java, Haskell, and Go.
Edit: I would even say that if Rust's performance is comparable to GC'ed languages, there's no point in using Rust in the first place. Rust is paying a lot of complexity to achieve memory safety without relying on garbage collection.
> Edit: I would even say that if Rust's performance is comparable to GC'ed languages, there's no point in using Rust in the first place. Rust is paying a lot of complexity to achieve memory safety without relying on garbage collection.
Rust has a lot of nice safety features not related to memory management that I'd love to see even in a language with GC. On top of the obvious lack of data races, linear types and borrowing are great for ensuring other kinds of sanity like preventing you from mutating a collection while you iterate over it.
Technically Rust doesn't have proper linear typing, only affine typing. Destructors give you "basically" linear typing, but there's ways to bypass destructors both explicitly and accidentally (though if memory safety isn't at stake, this isn't a worth worrying about IMO):
> I would even say that if Rust's performance is comparable to GC'ed languages, there's no point in using Rust in the first place.
I'm not sure that's a fair assessment.
Even if you didn't gain anything in terms of raw performance, the gains in performance predictability would probably still be enough for many applications.
Even if you didn't gain anything in terms of performance at all, the ownership semantics also give you good safety characteristics around concurrency, which would still probably be worthwhile for at least some applications.
I think good support for SIMD will go a long way. C++ is also pretty slow on its own, but with SIMD intrinsics (or lucky autovectorization [1]) it can be speedy. We're talking typically about 1.5 - 10x speedup in performance sensitive code with SIMD vs plain C++. Highest speedup I've seen was about 40x in sorting code. SIMD is a sorting demon.
That said, I want safety more than speed. Speed I can get by writing an unsafe extension in C/C++. Safety is very hard to achieve in C/C++. Getting it right is the first priority. That's why I see Rust being used instead of where traditionally C++ is the default. Or some other new language that can fill C++'s shoes. Rust does seem like the strongest candidate at this point.
I'm confident Rust and Golang performance will improve as the projects get more mature. The generated code is full of optimization opportunities.
[1]: Autovectorization optimizations by C++ compilers tend to be fragile, small changes will undo them. So far easier to use intrinsics than figure out the black magic for autovectorization optimizations.
Note that SIMD support is currently in the Rust nightlies (as a result of Huon Wilson's summer internship at Mozilla), though I don't know what the timeline is for exposing that support in a stable release.
Yeah, but that's only because teXitoi's and Joshua Landau's improvements to many Rust benchmarks are still open on the tracker. And it's still using 1.3 (Ok, 1.4 only came out yesterday). Note that this is not meant as an indictment of the maintainer, who is indeed working very hard to keep the site usable, accurate and useful.
In languages like C and C++, you have precise control over memory allocation and pointers. If you want to say, "My data structure should contain exactly these bytes, and be located here", then you can easily do so. This low-level control makes C and C++ very fast. Similarly, the language does very little "behind your back." It's why most kernels, interpreters, web browsers, compilers and AAA games are still written in a low-level language like C or C++.
But this control comes at a price: You need to be very careful. You need to manage memory manually, you need to avoid accidentally writing into memory you don't own, and so on. Overlooking small details results in security bugs.
Rust attempts to provide the same low-level control as C and C++, but it adds safeguards which keep you from shooting yourself in the foot. In "safe" mode, all of these safeguards are fully engaged. In "unsafe" mode, Rust allows you to do some sketchy things. The idea is to use as much safe code as you can, and only use a tiny amount of unsafe code if you need to do something Rust can't prove to be safe.
For example, there's a great series of blog posts about writing a toy OS kernel in Rust: http://blog.phil-opp.com/ The author uses some assembly to set things up, and a tiny amount of "unsafe" Rust to directly access VGA memory. But all the higher level code is safe, which rules out a whole class of bugs.
Personally, I've spent a lot time using both C++ and functional languages. And for me, Rust hits a sweet spot: I can get down to the metal when I want, and I can build nice abstractions. And I can write almost all my code in "safe" mode, and not worry about dumb pointer bugs. It's an interesting combination and it makes me want to hack on new things. :-)
As far as I know there are not a lot of options, either you explicitly free memory and have low level control (malloc and free etc) or implicitly free memory and use GC.
Modern C++ programs should never write a new or delete anymore, except in very particular circumstance.
You should always be using make_unique and make_shared for heap allocations and you use shared whenever an object is being passed around and needs to track its owners, unique is for single-usage-instance use cases.
Those smart pointers have minimal overhead, deterministic runtime, and have no more overhead than manual implementations of the same mechanisms with raw pointers.
That is also how Rust does it - references are smart pointers, so you avoid having a GC.
Being able to handle a large self-referential data structure automatically is never cheap. So, you need to either have complex GC, a manual approach, or leak memory.
To elaborate a bit, references are "smart pointers" in the sense that they have extra semantics on top of regular pointers, but they also aren't like what most people refer to as smart pointers.
Rust's &T (references) are entirely checked at compile time, and generate the exact same code as a regular old C pointer would. No wrappers, extra allocations, reference counts, move/copy/whatever constructors (not that Rust even has those, but are important for C++ smart pointers).
Rust has a particular meaning of 'unsafe': https://doc.rust-lang.org/book/unsafe.html There are just 3 things it allows you to do, but that's all it takes to get the necessary bits of potential-foot-shooting bits out of C/C++. One simple use case for dereferencing a raw pointer is if you're working with an embedded system. You know (through specs) that address 0x00b1 in memory corresponds to a physical button press on the development board, so you write a loop that continuously dereferences this address and does something based on whether the dereferenced value is 0 (pressed) or 1 (unpressed). As far as the source level is concerned, you're just dereferencing a magic address pointing at who knows what value over and over again and never changing that value either. "Unsafe" tells the compiler that you know what you're doing. (And sometimes even in C/C++ you'll need the 'volatile' keyword to keep their compilers from optimizing the dereference to only happen once out of the loop.)
Rust allows you define blocks of unsafe code in which certain checks aren't enforced by the compiler. In these sections you have pretty much as much freedom as you always do in C.
In addition to what's mentioned below (and you can read on the rust site) -- in a js frame of reference, consider == vs === and/or strict mode vs non-strict. Comparing Rust to C is a bit like comparing js code written in strict mode, using just === for comparison, versus non-strict, use only == - and perhaps always having a custom function that deals with arrays, using eval to marshal objects from strings stored in the array, rather than the object/type features that js provides (especially modern js).
It's not a perfect analogy, but perhaps gets a bit of the flavour difference.
To understand unsafe code you need to understand what's safe code and pointers first.
Basically, safe code is automatically managed to get rid of memory leaks [0], dangling pointers [1], null pointer dereferencing [2], race conditions [3], and many other memory management bugs.
You don't encounter any of these in JavaScript because it's a managed language, meaning there's a garbage collector [4] that looks after all your memory allocations.
Contrary to the managed languages, C & C++ are unsafe and "native" (meaning that they're very close to the hardware level) - it's a wild land where you manage memory manually yourself. If you miss a single deallocation, you're left with a memory leak. If you deallocate a memory region twice, you have a double free situation [5]. So you have to be very cautious when writing code in C & C++. There are many techniques to make it easier (e.g. smart pointers [6]), but not all developers use them.
Rust takes the middle ground: it combines safety with unsafety by using a know-how affine type system with a borrow checker, which allows values/variables to have only one owner. This system has properties of both managed and unmanaged code. "Unsafe" code in the context of Rust means that you consciously turn off the borrow checker, leaving on your own and managing memory deallocations manually, as in C. That makes it possible to interface your Rust code with the native code, at the cost of making you prone to all errors and bugs that are listed above. So it's a good thing to minimize usage of unsafe code, to allow the Rust compiler to check your code for safety.
How does it relate to writing C++11 without using raw pointers. I.e. using uniq_ptr (single owner), shared_ptr (multiple owners), references (not-an-owner). Are there benefits beyond that; and does it have the capabilities C++ has regarding making classes, operator overloading; templates?
> How does it relate to writing C++11 without using raw pointers.
Rust's safety features are similar to those, but even stronger in a number of ways. There's two aspects to this: 1. more guarantees. You can write code with uniq_ptr that segfaults, but you cannot with Box<T>. 2. safety allows for patterns that would be dangerous in C++. For example, shared_ptr uses atomics for thread safety. Rust has two variants, Arc<T> with similar atomics, and Rc<T> without. If you're not using multiple threads, you can use Rc<T>, safe in knowing that Rust will give you a compile-time error if you introduce multithreading. In other words, there are ways that you need to be defensive in C++ that you do not in Rust.
> does it have the capabilities C++ has regarding making classes,
Rust does not really have OOP, exactly, we have structs and traits (concepts). One major difference is that we don't currently have inheritance, though proposals are being worked on. But since we already have the concept equivalent, we have extra power there. Tradeoffs.
> operator overloading
Rust lets you overload certain pre-defined operators, but not create new ones.
Well and generics & traits. 99% of the places where C++ programmers use templates, they would use generics in Rust. `template std::vector<T>` <-> `Vec<T>` etc.
It's only the niche metaprogramming ability of C++'s templates that would be replaced with macros.
Rust's model is similar to a cleaned-up and guaranteed safe use of modern C++ smart pointers and references.
A few of the improvements that Rust offers are compiler enforced move semantics by default, so assigning, returning, or passing a Box<T> (similar to unique_ptr<T> in C++) will move the ownership to the new location and statically check that you never try to access the old location. In C++, you have to explicitly call std::move(), and checks to make sure you don't reference the old one happen at runtime.
Rust also does safer checking of use of references. In C++, it's possible to return a reference to a stack variable which is no longer valid. In Rust, the lifetimes of references are tracked statically, so you can never have a dangling reference.
So, while the RAII pattern, use of smart pointers, and use of references, will feel fairly familiar to someone familiar with modern C++, they are actually generally more convenient and more safe in Rust.
Rust does not have classes. You define plain old data structures, and use traits to provide polymorphic behavior. Traits are somewhat like interfaces which can contain default implementations of methods. Traits can be used to constrain types. Traits can be used to provide either static (compile time) dispatch, or runtime dispatch, depending on whether the object is monomorphized at compile time or accessed through a fat pointer that includes a vtable to dispatch at runtime.
Rust provides generics which fill a similar niche to templates in C++. In some ways, they are more powerful as arguments can be constrained by traits; in some ways they are less powerful than C++ templates.
Rust doesn't provide operator overloading directly, but use of operators is equivalent to just calling a method defined on a particular trait in Rust. So, "a + b" is will work for any types that implement the Add trait (https://doc.rust-lang.org/std/ops/trait.Add.html), and is equivalent to calling a.add(b).
You can still trigger plenty of undefined behaviour in C++11 with no raw pointers. Just one example--iterator invalidation:
auto v = std::vector<int> {1, 2, 3}
for (auto x : v) {
if (x > 0) {
v.push_back(-x);
}
}
The above may segfault, do what you expect it to, run forever etc. Unchecked indexing, branching on uninitialized data etc.
Rust frees you from _all_ of these things and offers a bunch of further advantages, most notably: data race freedom. You can actually share data between threads and keep your sanity.
The vast majority of C++ template usage is acheived by generics, and in a much more type safe and debugable manner.
Most of the remaining things can be achieved by Rust's hygenic macros (which are not like C++ macros, they're more like C++ templates in how they can be used and the guarantees they provide).
> . So you have to be very cautious when writing code in C & C++. There are many techniques to make it easier (e.g. smart pointers [6]), but not all developers use them.
Not everyone has security as a requirement. Some people jsut want to write something that is both fast, predicable. Most banks don't make any security demands of internal applications because they are not accessible to the internet. At which point safety is just a way to slow you do.
>>Most banks don't make any security demands of internal applications because they are not accessible to the internet. At which point safety is just a way to slow you do.
This is a bad way of doing things. That means as soon as someone finds a way to get inside the network, the bank is wrecked. You need to look at security from the point of assuming the attacker somehow got inside because it'll happen one day.
"""
MOTHERBOARD: How did you hack Avid Life Media? Was it hard?
The Impact Team: We worked hard to make fully undetectable attack, then got in and found nothing to bypass.
MOTHERBOARD: What was their security like?
The Impact Team: Bad. Nobody was watching. No security. Only thing was segmented network. You could use Pass1234 from the internet to VPN to root on all servers.
"""
Let me know which banks you've worked at with this attitude. I just want to check I have no accounts with them.
This most definitely is not the view taken at the financial companies I've worked in/with including a number of banks.
They take internal security very seriously. There's the obvious threat of some form of break-in (no-one I know seriously believes that an unbreachable connected system is buildable). But there's also the issues of insiders taking stuff, Chinese walls and regulatory restrictions on access. It simply doesn't make economic sense to take security lightly any more.
On the other hand, banks want numbers that actually add up properly, at least in the code that deals with money. If you use undefined behavior in C or C++, the languages do not guarantee that your bank account balances won't all be rounded down to the nearest prime number on Thursdays in odd numbered months in leap years.
Others have commented that security type safety can be important even in applications that are not externally facing, but thats even sort of besides the point. When people talk about 'safety' in Rust, often times they are talking about the predictability that you mention. Highly threaded code can seem to run perfectly for some time before obscure bugs pop up. Rust's abstractions around thread and memory safety can help in those situations.
I'm going to have to disagree with your point about banking applications based on my experience (~15 years) in banking IT and security.
Whilst obviously Internet facing apps are treated with more caution, it's definitely not true in my experience to state that there are no security demands on internal applications. I would expect that most/all banks these days realise that there's no such thing as a fully trusted network...
This is awesome. Now to go dig up my WAD files. To add to the list of languages where bringing up Doom was helpful in vetting the language, Java. Patrick Naughton built a BSP rendering platform in Oak using the Doom WAD files and found a number of places where the JVM needed tweaks.
It's the same project, I just been keeping it up to date and removed all the unsafe code. It never _needed_ unsafe code, but it was my first Rust project and I didn't know what the hell I was doing---it's now much cleaner, safer and faster than the incarnation you saw last year :)
That's awesome! I'm just learning rust and I'm really enjoying it, though it was more challenging getting into the right mindset after working on GCed languages for most of the last 4-5 years. Certainly wrestled with the ownership system for a while but it's all making a lot more sense now, and I'd much rather have the compiler telling me about these problems than incorrectly dereferencing memory in C code.
It's hard to quantify how much time _this_ project took, since when I started a year ago I knew nothing about Doom or Rust. If I started writing it now from scratch, I could probably hammer it out in a working week. A better programmer could probably do it in a weekend. It's neither very big nor very hard.
But that's not how programming works, is it? In fairness, you'd need to count some of the time I spent working on a lot of other random Rust side projects and reading blog posts over the last year, while only spending a couple of hours ever other week or so on rust-doom.
> Certainly wrestled with the ownership system for a while
I think my background in modern C++ helped me a lot with this, since the "one owner per thing, move semantics" model is how you're supposed to write C++ too.
That said, figuring out the cleanest design is not easy and I'm still wrestling with it today. The code was an absolute mess last year and now it's better, but still needs plenty of work: just yesterday I decoupled WAD parsing from the rest of the code properly (https://github.com/cristicbz/rust-doom/pull/53).
I'm still learning a lot a year after dedicating all my free-time programming to Rust.
This is great. I've wanted to start a project in Rust but I'm hesitant since I haven't found any profiling tools akin to VisualVM. I'd feel a bit blind writing multi-threaded code where I can't see what's going on. What do people use to sanity check this sort of thing?
What a great project! The author should do a talk about his experiences. I searched for something like this on YouTube, and only found this: https://www.youtube.com/watch?v=0_-TRvbo54Y.
I love stuff like this. I and some other students made a Quake 3 engine in C# using OpenTK (OpenGL 4) in about 6 weeks. None of us had ever done real game development before. It was a great learning experience and overall we're pretty happy with what we accomplished.
It's still very preliminary and not optimised, but I might tinker away at it now that my degree is all but done. At this point it can load and render any Q3 (or OpenArena) BSP map, but most all other things are missing.