153 comments

[ 3.9 ms ] story [ 206 ms ] thread
I like that the author threads in so many other concepts with links to other blogs that explain those concepts.

I really need to get my hands dirty with Rust. A lot of very smart engineers have been throwing projects my direction seeking opinions and I've never been able to properly offer any. The folks working on the language and projects around the language are really sharp.

Recently I haven't had any personal projects that aren't a conglomeration of API requests and simple data storage. Rust is more than capable of doing this kind of work but I'd really like to find a pet project that would take advantage of it's performance capabilities.

For me, is build a relational language (http://tablam.org) (like a kind of data engine, like sqlite but not storage). I never do that kind of work before (ie: language/rdbms) and is a eye opener.

I can't denied rust is hard and a lot of time I get lost thinking "why rust don't let me do...". But is truly a good teacher...

This post makes me extremely happy. :D

> among the sites of its peers, only Ruby is similarly localized. Given that several prominent Rustaceans like Steve Klabnik and Carol Nichols came from the Ruby community, it would not be unreasonable to guess that they brought this globally inclusive view with them.

Not just that, I actually looked at how Ruby's website did it in order to bring it to our site. This has... issues. But it's served us fairly well.

Additionally, while there's so much more I could say about this post, one thing:

> my naive Rust was ~32% faster than my carefully implemented C.

This is a metric I'm more interested in, far more interested than classic benchmarks, where various experts eke every last little bit out of an example. What's the average case here?

It is, of course, much much harder to get numbers for these kinds of things...

I talked to some embedded C programmers and they said thad basically everything besides C is too slow for their use-cases.

I would be interested if they're wrong, hehe

In general, if Rust is significantly slower than equivalent C, it's a bug. We do care about bugs, and if you try some stuff and find them, please file them. (We track them via this tag: https://github.com/rust-lang/rust/labels/I-slow)

Embedded can be tricky at times; we've had a working group pushing on making stuff great all year. There's still a lot of work to be done. Sometimes you need to opt into nightly-only features. We'll get there!

(One major example is platform support: since we're built off of LLVM, we may not have a backend for more obscure architectures. ARM stuff works really well, generally. AVR is down to some codegen bugs...)

Are there theoretical boundaries that prevent Rust from ever reaching C performance or is this just a practical question of compiler optimization?
Not qualified to answer, but I did notice recently that Rust appears to generate more compiler code than equivalent C, but I don't think this realistically has a negative performance impact. You can check this out on godbolt (https://godbolt.org/)
It's possible to set compiler flags to optimize for size. That said, I've also seen large binaries, and monomorphization can contribute to that. Some projects are taking that more seriously, for example miniserde is designed to explicitly minimize monomorphization and this improve both code size and compile time.
Hadn't heard of miniserde before, awesome find, thanks!!
It really depends on exactly what you mean.

There's one significant thing that's in C that's not in Rust: alloca. If you need that, well, we've talked about it, but haven't accepted a design, and some people never want to gain it directly.

EDIT: another comment pointed out goto; I have no idea what the performance implications are but I’ll have to add that to my list of “stuff C has that Rust doesn’t.”

Sometimes, you need nightly-only features. This means that, in some sense, today's Rust is slower, but tomorrow's Rust may not be.

Sometimes, "equivalence” is the issue. You can turn off bounds checking for array access, for example, but most Rust code doesn't, for hopefully good reason. Sometimes those checks are elided, and so it's exactly the same. Sometimes they're not. Sometimes they're not and that inhibits other optimizations. Is unsafe Rust "equivalent" to the C? Or does that not count? (Unsafe Rust is not always faster than Safe Rust...)

At the theory level, I can't think of anything off the top of my head that would inherently make Rust slower than C, generally speaking. There's also some degree of argument that in theory, Rust should be faster than C, thanks to how much more we know about aliasing, etc. That's a whole other can of worms...

> Sometimes those checks are elided, and so it's exactly the same. Sometimes they're not.

This is something I've personally thought about a lot and I have no idea how I'd actually want it implemented either on the backend or in the syntax, but I think it'd be useful to have some way to say "I want these bound checks to be elided, if they can't be then make that an error". Similar to how you'd decorate a pure function in other languages to say that it can't cause side effects or depend on side effects for what it's doing.

I think that's probably a high level ask that's probably significantly more difficult that it seems initially too.

Can this sort of information be gotten out of LLVM?
You probably just want a dependently typed language. Proving bounds at compile time is an introductory exercise for the likes of Idris.
ats [http://www.ats-lang.org/] is a C-level language with dependent types
Note that ATS has a limited version of dependent types as compared to Idris/Agda/Coq/etc. As I understand it, you can't run arbitrary (recursive, etc) functions at the type level. But even this 'lite' version lets you express preconditions like "n is a multiple of 4" or "the source array and destination array must not overlap in memory".
Quite possibly that's what would work. I haven't tried any of them in any kind of serious capacity before.

Edit: Looks like there's been an RFC in the past to add it to rust, but it got punted because the const generics stuff went in first and they wanted to see how that would shake out before trying to fully tackle this. https://github.com/ticki/rfcs/blob/pi-types-ext-2/text/0000-...

> EDIT: another comment pointed out goto; I have no idea what the performance implications are but I’ll have to add that to my list of “stuff C has that Rust doesn’t.”

On that front, one interesting thought would be to reimplement the CPython bytecode interpreter in Rust and see if you can match the performance of C using computed goto.

In principle, there's no fundamental reason Rust couldn't optimize "loop around match" exactly the same way, without needing the computed goto. (For that matter, so could C.) Doing that would help cases like this.

Well, the answer should be "no" for unsafe Rust, since you can mechanically translate any C to unsafe Rust and vice versa.

For safe Rust, sure, there are limitations: you can't turn off bounds checks, for instance. But all Rust is partially unsafe Rust, because the standard library uses unsafe code ⃰. So it's a fuzzy distinction.

⃰ ⃰You wouldn't want it any other way. Implementing low-level functionality as unsafe code allows us to write code, not compiler-code-that-generates-code.

In a sense you can mechanically translate between any Turing complete languages, but there's not a "naive" translation, so to speak, because C can have irreducible CFGs and Rust cannot. It's not clear to me that reflowing a CFG can always be done without a performance impact.
True, I forgot about goto.
Not only true goto but also switch and computed goto. AFAIK C/C++ are essentially the only games in town for these. No modern language has even corrected the purely syntactic limitation that prevents nested switches (by eg. allowing you to attach label names to switch blocks and cases).
You can convert an irreducible CFG to a reducible CFG with a loop and a switch case(/match in case of rust):

In pseudocode

  Goto = Start;
  While(Goto!=End) {
    Match(Goto) {
    ...
    }
  }
It’s a pretty basic obfuscation technique. Compilers will happily unroll this leaving you with an irreducible CFG.
You have more faith in the compiler than I do. Here's a stupid test I made.

* In C++, using goto: https://godbolt.org/z/YqCMbU

* In Rust, with relooped CFG: https://godbolt.org/z/IIXtKo

The compiler was obviously not able to unroll the relooped code into the original CFG.

A specific example: in principle, the compiler could see the exact target of every continue in the Rust code, so the continue on line 34 could go directly to line 27 (or, better, as in C++ the basic blocks could just be laid out adjacently), but the compiler does not actually do this and there are a bunch of unnecessary tests on the path between those two lines.

It's unfortunate that the enum-based solution doesn't work, but that particular example can be made "optimal" (at least, as good control flow as LLVM can get this code: the same as clang, not GCC), by using a rather unpleasant series of loops and labelled breaks: https://godbolt.org/z/sQ1nH8 (rustfmt'd version: https://play.rust-lang.org/?gist=b1091e0c583b88f27bf1eaeae3a...).

This doesn't work in general, and is ... impossible to maintain, but it's not an unreasonable approach for generated code.

Note that Rust has "forward goto" in the form of "labelled break" (which can even carry a value), so I suspect some cases might not even need converting to a loop-match "state machine".
note that when you use iterators, bounds checks do get elided.
Bounds checking will inhibit vectorization, panicking will bloat code, maybe I'll make a comparison against a rust coreutils bin vs something like sbase.
For some of the unsupported targets there is the Rust->C compiler. Haven't used it myself but I've seen that, for example, the ESP32/ESP8266/Xtensa is supported by it.
That's true, but the issue with it as a real development option is that its job was to bootstrap the compiler, and so it implements Rust 1.19 (I believe...), and nothing further. So you're missing out on a lot of useful stuff.
Hey Steve, thanks for commenting here. As a person who is trying to get into embedded development but who knows very little, can you explain the challenges of having a language support most/all embedded platforms? I’m guessing you would need to support old versions of gcc that or something like this? Or be able to compile things with each platform special flags?

It seems like even C projects have a hard time being portable. People even avoid cmake because of the large dependency and the fact that it is cross platform until it isn’t.

Not Steve, but do know or or two things about embedded and portable C code.

First of all, most embedded development makes use of bare metal, where the libraries take the role of an OS, or they use a specialized OS from the hardware vendor.

Just using pure ANSI C isn't possible, because the standard does not expose the hardware features from the underlying platform, so the alternatives are to use Assembly, or language extensions.

Naturally language extensions are more convenient to use, so that is what most developers end up doing.

Also there are many types of embedded platforms, you can be targeting anything between a tiny PIC with 8KB FLASH RAM to a powerful multi-core ARMv8-A with 8GB.

So the toolchain must allow for customization of what actually gets linked into the final binary, and the runtime must be as thin as possible.

Then there this the drivers story, each vendor gives you their own SDK, which most of the time is the only way to access their devices.

It is typical for open source projects to reverse engineer some of those SDKs to get the necessary information for linker maps, compiler flags and driver information.

Regarding Rust, there is an ongoing effort to create a embedded library for hardware drivers, as means to write portable code.

https://github.com/rust-embedded/awesome-embedded-rust

The first issue is to have a compiler backend for that target. Someone has to write that. gcc supports more targets than LLVM.

Then, you have to make sure that it doesn't break; this means running on that target in CI, somehow. Given that you're already talking about devices that may not even have an OS... emulation can work sometimes?

Then, there's ecosystem stuff. You probably want some sort of HAL and support for not just one platform, but all of the platforms you're deploying to. So that's more work...

Finally, some platforms are proprietary and basically give you their own fork of gcc and so C is pretty much your only option anyway.

Regarding performance: For my first foray into rust, I recently tried reimplementing a simple C++ tool in rust. It reads pcap data from stdin, finds the vlan tag for each packet and writes packets to one of a few different files (or stdout) depending on the vlan id.

I know C++ quite well, and I'm confident that the C++ version is reasonably well optimized, though it probably has a little room for improvement.

In rust, I think I'm doing things in a reasonable fashion but so far the performance is only half of the C++ version. So, not bad, but I was hoping it would be closer. I'd like to know if anyone has any suggestions for resources related to rust optimization.

You can use your usual tools, like perf, on Rust.

What’s the situation with the IO? Are you buffering? Are you holding the lock for a long time or rapidly locking and unlocking?

If you can share the code I can take a look.

I realized that stdout was acquiring and releasing a mutex on every write operation; fixing that improved things. The good news is that when writing to /dev/null, it's now faster than the C++ version, but writing to stdout is still ~25% slower. I suppose there's something else suboptimal about the way I'm using stdout, but I haven't figured it out yet.

Code is here: https://gist.github.com/usefulcat/56f334bc58c97edb073b457b68...

There is actually one other file but it only contains a couple of struct definitions for the libpcap file and packet headers. Thanks for having a look!

Update: I wrapped the locked stdout handle in a BufWriter and now the rust version is >25% faster than the C++ version in all cases.

I didn't think there was room for that much improvement; I'm really impressed.

Awesome! Glad you got it sorted. Those two things are always footguns...
If you have a blog, that would make for an amazing entry, going from 50% slower to 25% faster.

Edit: HOLY CRAP! I have a program written in Rust, ppbert[1], and I just tried wrapping my StdoutLock object in a BufWriter, and I improved the performance of my pretty-printing by a factor of 2x! I knew to use BufReader for files, I didn't know it was helpful for stdin and stdout! Thank you _so much_ for sharing your experience, I've certainly benefited!

    Benchmark #1: ppbert -2 *.bert2
      Time (mean ± σ):      3.816 s ±  0.115 s    [User: 2.494 s, System: 1.321 s]
      Range (min … max):    3.688 s …  4.028 s

    Benchmark #2: ppbert-dev -2 *.bert2
      Time (mean ± σ):      1.728 s ±  0.045 s    [User: 1.493 s, System: 0.234 s]
      Range (min … max):    1.678 s …  1.843 s

    Summary
      'ppbert-dev -2 *.bert2' ran 2.21x faster than 'ppbert -2 *.bert2'
[1] https://github.com/gnuvince/ppbert
Can you share the code? Are you using the `--release` flag to build/run it?
They probably are not as their C probably doesn't do much by modern programming standards.

The real questions are whether Rust would improve the performance of things like a Bluetooth Low Energy communications stack given that communication stacks are probably the most complex things that run on a Cortex M0 or M4 class processors.

In my experience speed is rarely the problem for embedded systems - usually it's more about code size. And tooling.
I'm an embedded engineer, who's been tinkering a bit with learning Rust; so far only for an application-level project, but am keen to try it out in embedded. So far, it's been fun and exciting, along similar lines as Bryan wrote about in the OP. In fact, a video of a talk he did (https://www.youtube.com/watch?v=LjFM8vw3pbU) is what pushed me to getting started.

I think you're spot on with regards to tooling being a major concern, and to me, this is one of the promising aspects of Rust, with cargo at the high level and the LLVM tools lower down. It seems like a system that's primed for open source libraries targeting specific families of chips, cleaning up the mess we've got now with each vendor having their own framework, IDE, configuration tool, etc.

Speed and size concerns though, seem to depend a lot on the specific project. In the last couple years, I've worked on systems that had neither/either/both of those as the primary challenge. Of course in embedded, where you try to save money on parts early in a project can determine the technical (and so, schedule) challenges later on.

And, I'd add safety to the list, having spent loads of time finding bugs in legacy C caused by stuff like walking off the ends of arrays, jumping to uninitialised function pointers, fun with enums and unions, etc.

The promise of cargo libraries supporting various chips and hardware would be a fantastic boon. Likely it’d significantly reduce security bugs and speed up dev time. Currently I’ve started using forth as a wrapper over embedded C libs so, well, I don’t have to write C daily.
Exactly. In most embedded contexts you have CPUs which are overpowered for what you need. What matters is code size, because flash memory is really expensive and the quality of your tools.

Also, once you go beyond blinking a couple of LEDs and especially if you care about energy consumption, you quickly get mired in complexity, dealing with interrupts and resulting concurrency. If you're a seasoned embedded developer, you will have an explicit, mostly declarative state machine at this point, possibly nested, and if you have more than 15 years of experience, you will be seriously thinking about better representations and generating all that C code from a Lisp.

I'm have a lot of hope for Rust in the embedded world. I especially hope that it will get embraced by vendors. To take a practical example again, I'm thinking about Nordic Semiconductor, which has really good SDKs and is innovative.

> my naive Rust was ~32% faster than my carefully implemented C.

In all fairness... the "carefully implemented C" was all wrapped up as an external lib for the node application they reimplemented so no having to convert values to/from js and deal with all that other stuff.

I think for the purposes of the benchmark he ran the C and Rust binaries directly. So it really was C implementation vs Rust implementation.
> my naive Rust was ~32% faster than my carefully implemented C.

One thing that we have noticed in our work on our Rust AV1 encoder, rav1e, is that Rust autovectorizes a lot better than C. In particular, it does a better job of proving that slices have nice sizes (e.g., multiples of 8 or 16 elements). That allows it to leave off all of the boilerplate for handling odd sizes. Rust also does not automatically promote every arithmetic operation to an int. That makes working on 8- or 16-bit types a lot more efficient. The stronger type system, memory safety, and the preference for idioms that avoid bounds checks mean the compiler can successfully autovectorize a lot more code than C, too. We used to joke that the only C code that gets autovectorized is the initialization loops that run exactly once and just set everything to zero.

It's still not as good as hand-written SIMD (maybe within a factor of 2), but it's a lot better than no SIMD.

That said, it makes it hard to do straightforward performance comparisons with C, especially for code where you know you will write the hand-written SIMD, but you just haven't gotten to it yet. There is a lot of code like this in a freshly-implemented video encoder. Rust is using a tool, autovectorization, that it won't get to use when you manually vectorize, and C isn't. That threw off a lot of our early attempts at benchmarking Rust vs. C.

Of course, most things are not video encoders, and most code won't be manually vectorized, so the better autovectorization is an absolute win in the real world.

> This is a metric I'm more interested in … What's the average case here?

The metric "my naive Rust" vs "my carefully implemented C" might be OK, once you figure out a consistent way to check code passes as "my naive Rust" or "my carefully implemented C".

Until then it's just an anecdote.

Might there be programmers whose "naive Rust" performs a lot worse than their "carefully implemented C"?

Might that be unsurprising to them and uninteresting as a blog post?

The naïve Rust part is explained in a video [1] that Bryan released a couple of weeks ago; instead of using bespoke data structures for his problem, he shoved everything into HashMaps and BTreeSets and used numerical indices rather than pointers to "emulate" a doubly-linked list. It turned out that was good enough to beat C.

[1] https://www.youtube.com/watch?v=aWbGPMxs0AM

> … he shoved everything into …

Did he do the same in C?

No, he had intrusive pointers; his rectangle nodes were in a couple of doubly-linked lists and an AVL tree at the same time.
So could we paraphrase as — maybe his "naive C" would be faster than his "carefully implemented C" ?

He just wasn't comparing apples with apples?

Honestly watching Brian talk about Rust https://www.youtube.com/watch?v=LjFM8vw3pbU is what finally inspired me to get the books and give it a go.
Thanks for posting that. Great rant/talk.
Hah, I think a lot of bcantrill's talks could be classified as rant/talk hybrids ;)

Here's his 2011 diatribe against Oracle (after they bought Sun, which he worked for at the time):

https://youtu.be/-zRN7XLCRhc?t=34m00s

Yep that one is pure gold
Can you tell what books/resources you have used to pick rust?
Honestly so far "The Rust Programming Language" book proved to be the most useful resource.
For what it's worth, these observations track closely with my own experience. I like that Cantrill emphasizes values rather than simply technical decisions. The Rust community, I believe, tries to get things right.

There's a learning curve, but with much experience, I find myself naturally thinking and writing in Rust code. I can get in the flow, and it feels just as high level and productive as languages prized for those qualities. I'm happy to see other very talented programmers have similar experiences.

It's kind of weird to say m:n threading is terrible when it seems to work okay in Go.

It wasn't the right choice for Rust, but that doesn't mean Go's choice was wrong.

As with everything, it's a tradeoff. You're right that it's working well with Go, but that's because Go is willing to accept the downsides of that choice. And they've done some stuff to minimize those downsides.

I think both languages made the correct decisions for what they're trying to accomplish as a language.

Go solved the main downside the article cites with M:N threading- it replaced split stacks with growable stacks, via a precise garbage collector.
That solved the hot split problem, but that was more of a once-in-a-while gotcha. The main downside for Rust specifically was the complications that arise when doing any sort of C interop in the presence of growable stacks. This mattered more for Rust than Go, since Rust is relatively more intended to freely intermingle with C code (and other languages via, C FFI), whereas the happy path for Go tends to involve exclusively Go code in a given process (even going so far as to reimplement their own libc in Go), and use of cgo is discouraged by the community (https://dave.cheney.net/2016/01/18/cgo-is-not-go).
nit: the author describes the '?' operator as both not-magic and magical.

IMO an operator that returns is pretty magical. What do others think about '?'? It's certainly more ergonomic than pattern matching / monads etc, but it also seems easy to miss if it's buried in the middle of an expression.

I think Swift's try is a bit nicer to use, because it comes at the beginning of the statement, provides more flexibility in how the error should be handled, and supports multiple expressions that may generate errors. On the other hand Swift's error handling is an addition to its type system, instead of being built inside it like Rust.

I'm unsure what "miss" means here. If you forget to add a ?, it's a compilation error.
When I'm reading code, I may not realize that an expression can return, because it can be hard to spot a ? embedded in the middle of the line:

    sanitize(getFile(getName())?.contents);
Swift requires prefixing the line with try, which makes it easier to notice:

    try sanitize(getFile(getName()).contents)
Both are a big improvement over exceptions which have no indication at the call site.
In Swift you can still bury that `try` in the middle of the expression

  sanitize(try getFile(getName()).contents)
Bit of a correction, I believe that `try` in Swift must precede an expression that can throw, rather than preceding a statement that can throw, so AFAICT the Swift code would have to be written like this:

  sanitize((try getFile(getName())).contents)
This sort of illustrates why Rust moved away from its own prefix `try!` macro to its postfix `?` operator: in a language that encourages chaining methods, `foo()?.bar()?.qux()?` looks better than `try!(try!(try!(foo()).bar()).qux())`.
No, `try` can be applied to any expression, and will "try" all throwing calls in subexpressions of that:

  func f() throws -> Int { return 0 }
  func nothing(_ x: Int) {}
  func g() throws {
    nothing(try f())
    try (f() + f())
    try nothing(f() * f())
  }
This includes method chains:

  struct Foo {
    init() throws {}
    func bar() throws -> Foo { return self }
    func qux() throws -> Foo { return self }
  }
  try Foo().bar().qux()
Cool, thanks, I looked in the official docs but didn't notice any mention of this feature. :)
Not sure how many editors' syntax highlighting do this by default, but at least for me, it really helped to make `?` bold red.
"Code is read much more often than written" ;)
I think ? is a perfect example of Stroustrup's Rule: https://thefeedbackloop.xyz/stroustrups-rule-and-layering-ov...

> For new features, people insist on LOUD explicit syntax.

> For established features, people want terse notation.

I was pretty against ? when it was proposed. I liked try!. I thought ? was hard to read. I thought try! was concise enough. After writing (and reading) lots of code with ?, I'm all in. Heck, ? still isn't extendable for user-defined types, and now I want that! We made ? work in main, and in tests.

It's now one of my favorite features of Rust. YMMV.

Rust seems to follow Stroustrup's Rule very well as a community. Might explain why I like it so much.
FWIW I don't disagree with Rust's choice, I was just answering the question of parent. I am reading Blandy and Orendorff's Rust book and violently agree with almost all design choices, as an experienced C++ programmer.
The gp almost certainly means when reading code, not when writing it. Why did this function return here... I don't see anything. Oh wait, there's a ?.

On the other hand error handling can distract from the intention of the code, so in another sense ? makes it easier to read.

The author can choose if they prefer ?, try!, or a plain old match or if let / return on the Result.

If you see `?` as a monadic bind (some kind of .andThen, see futures / promises), the whole thing reads and makes sense: the rest of the expression is a function call, a lambda that only gets invoked when the `?` passes a value through.

So `foo?.bar` is something like

    match foo {
      Ok(inside_foo) => Ok(inside_foo.bar), // .bar the lambda.
      Err(v) => Err(v),
    }
The `do` notation makes it a bit more explicit. It also looks directly lifted from Haskell's approach to work with monads.
I've been dabbling in Go, but I keep wondering if I would prefer Rust after reading articles like this...
I dabbled in Go for a while too, but found I much prefer Rust, Elixir, and Crystal (even though it is still a bit immature). I know a lot of people love Go, but these other three languages speak to my soul.
You're making this decision even harder! I used Go a bit in the course I'm teaching this semester, so maybe next semester I'll teach with Rust :)
How does the concurrency model in Rust hold up when compared to Erlang/Elixir? Crystal's concurrency seemed lacking the last time I looked (which was a while ago).
The biggest difference is that Rust doesn't force a specific one on you. The language knows nothing about threads. It does have two special traits, Sync and Send, that should be used by any thread-related APIs for concurrency guarantees, and the stdlib thread API does use them.

But there are other concurrency/paralellism models beyond std::thread, including really cool stuff like "scoped threads", and more classic "single threaded event loop" stuff, or thread pools.

The big differentiator is that Rust proves code free from data races at compile time. Not the specific model.

Incidentally, you can write NIFs in Rust...

This begins to frame one of the problems I have lately. Dabbling in Go, dabbling in Erlang/Elixir, dabbling in Elm, dabbling in Rust...
From one dabbler to another, try dabbling in Common Lisp sometime, it might help in settling down...
You should! I used Go on a sizeable side project, and while I did enjoy it, I much prefer Rust. There's no doubt that Rust is harder to learn, but not pointlessly so; it's a learning exercise, especially if it's your first foray into low-level systems programming. Be patient, the payoff is worth the investment!
I'm happy that error handling landed in first place; I feel the same way. Rust is the first language I've used with ADTs, and they're incredibly expressive. It allows the error system to be a set of idioms built atop an expressive type system (okay, so the ? operator does add a little sugar), rather than a discrete language feature, like with exceptions. It brings error handling to the forefront, and it's a big reason why people tend to say "if it compiles, it works" when talking about Rust.
One of the things that's turned me off about Rust is that a lot of code seems().to().chain().lots().of().operators()

Is this idiomatic? It seems powerfully indecipherable/illegible

It's often idiomatic. I've never found it to be indecipherable at all. I actually find it to be quite readable in most contexts where intermediate variables are just noise.

Iterators are a typical example of this.

vec![0, 1, 2, 3].into_iter() .filter(|x| x % 2 == 0) // Keep the evens .map(|y| y + 1 ) // Add 1 to elements .rev(); // Reverse the iterator

Adding intermediates would just add noise for code like this.

It's idiomatically done with newline separators, e.g:

    let sum = vec_of_ints
        .iter()
        .filter(|x| x > 5)
        .map(|x| x.pow(2))
        .take(3)
        .sum()
That is, iterate my vec, only consider values > 5, square them, take the first 3, them sum them.

Consider the imperative code:

    let mut sum = 0;
    let mut n = 0;
    for x in vec_of_ints {
        if x > 5 {
            sum += x.pow(2);
            n += 1;
            if n == 3 {
                break;
            }
        }
    }
I think the chained combinator approach is much easier to see exactly what's happening (and is often higher performance!)
Well, dunno, in my opinion both are equally obscure. I personally don't like "dot notation" and that's one of the reason I don't like Rust. The main problem I see with dot notation is the Pigeon hole problem: why sometimes you write x(y) and sometimes y.x()? While the functions in the above example are trivial, there exist not so obvious cases.
That's nice and all. But then you have abominations like this:

    state
        .db
        .send(Queries::GetTopTenHottestYears)
        .from_err()
        .and_then(move |res| {
            result.push(res.unwrap());
            state
                .db
                .send(Queries::GetTopTenColdestYears)
                .from_err()
                .and_then(move |res| {
                    result.push(res.unwrap());
                    state
                        .db
                        .send(Queries::GetTopTenHottestMonths)
                        .from_err()
                        .and_then(move |res| {
                            result.push(res.unwrap());
                            state
                                .db
                                .send(Queries::GetTopTenColdestMonths)
                                .from_err()
                                .and_then(move |res| {
                                    result.push(res.unwrap());
                                    fut_ok(result)
                                })
                        })
                })
        })
        .and_then(|res| Ok(HttpResponse::Ok().json(res)))
        .responder()
https://github.com/actix/examples/blob/master/async_db/src/m...

My suspicion is the people do this to avoid "variable does not live long enough" type errors.

I believe that is written that way because it's using futures. Once async/await lands this year, all of the futures combinator madness goes away.
This is called the 'pyramid of doom' in the nodejs circles
Doesn't the chained version still iterate over all the elements and squares them before taking the first 3 bigger than 5? the second version only goes over the minimum necessary elements and then breaks. How can the first be higher performance?
No, the iterators are "lazy" (pull-based). Both implementations will perform the same amount of work.

Oftentimes the iterator will be faster, though, because the compiler can reason about what you're doing easier with an iterator vs. iterating via manual sequential indexing.

(comment deleted)
It helps me with autocomplete. In Python at least, I find

  int.__add__(a, b)
is less idiomatic than

  a.add(b)
which removes the global abstract. Though I see it looks less symmetric.

I'm not a Rustacean - I only speak to the syntax difference.

One thing I've been missing in starting with Rust, is a basic GUI library. Conrod looks promising, but the apparent lack of documentation, coupled with being very new to the language, makes for a very steep learning curve.

Could anyone suggest where to start developing a cross-platform GUI program in Rust?

The problem with GUI is that actually exist very few good ones. And much less languages with a good implementation.

I wonder what if in https://www.reddit.com/r/rust/comments/9bapwt/thoughts_on_wh... how about use Rust for most of the UI logic and off-load the final steps for a native layer. I call it a "almost cross-platform" UI.

On mobile, so no links for you ;)

I've seen promising things about Qt/QML. Qml in particular helps avoid having to deal with lots of C++ interop.

GTK seems to be rallying around Rust, and it actually looks pretty good nowadays. relm is a really interesting elmish wrapper around GTK.

Others have put rust components behind an Electron GUI.

So tl;dr it's quickly moving from "terrible and immature" to "terrible, like other languages".

> I've seen promising things about Qt/QML.

For simple GUIs, the problem I always ran into was 1) spooky licensing vs 2) Tkinter vs 3) can this be forced into a browser?

> in particular helps avoid having to deal with lots of C++ interop.

I agree there is significant demand here. But I also feel a second (smaller) group wants access to GPUs for high-performance visualizations and/or games, since that's usually built in C or C++.

> "terrible, like other languages"

This made me laugh. I want to move out of the browser and back to the metal.

Thanks for the pointers!

I have used Qt a fair bit for hobby projects, and quite like it in the context of cross-platform C++. But I haven't yet wrapped my head around what a Rust interface to Qt would look like, and results searching https://crates.io for Qt bindings had kinda scared me off.

https://github.com/KDE/rust-qt-binding-generator looks quite interesting though!

> So tl;dr it's quickly moving from "terrible and immature" to "terrible, like other languages".

Ha! Well, at least we don't have so long to wait, I guess.

>"Over a decade ago, I worked on a technology to which a competitor paid the highest possible compliment: they tried to implement their own knockoff."

Could someone share who the competitor and what the knock off was?

Containers maybe?
Thanks, right he's a Sun Microsystems alumn. Two very brilliant projects(dtrace and zfs.) Cheers.
I was assuming zfs and btrfs
This resonated with me a lot:

> These values reflect a deeper sense within me: that software can be permanent — that software’s unique duality as both information and machine afford a timeless perfection and utility that stand apart from other human endeavor.

I recall reading a story about IBM systems programmers writing code for zSeries mainframes. A thing that stuck with me was a discussion about code at the core of this work; virtual memory, IO subsystems, etc. The engineer reflected on how the code he was writing would still be in production when he died. It would survive evolution of the hardware for many generations; they designed it to do this and provide permanent APIs to relying code.

I wish I had a copy (..link) to that article. Byte, Computer Shopper... something like that.

There are a few pieces of code I wrote in the 90's (in C) that I know are still in use and may survive a few more decades; as long and the companies that I worked for at the time are still in operation anyhow.

In the past week only there have been 33 posts about Rust. Y Comb loves to constantly push technology in short spurts.
I've been on something of a language tour recently - trying out C++, Typescript, OCaml each for a couple of weeks just to get a taste of living in each ecosystem. I haven't been actively following Rust beyond reading Hacker News articles (of which there are many) about people liking the language.

One of my pet peeves with several "newer" languages that I've looked at is that users don't usually talk about the ugly parts. You can try out C++ for a week visiting forums and r/cpp and you find out fairly quickly what the pain points are and what typical workarounds look like. At that point, it is up to you to decide to what extent you can live with those downsides and where to tread lightly. Same for C, Python, OCaml etc.

To be clear, I don't mean that you should go looking for the bad parts in a language but I do believe you should be aware of them before you invest a lot of your time in it. Unfortunately, a lot of the language love blog posts do not talk about the pain points of the language and what kind of problems it isn't well suited for.

I like Rust and I love several features that Bryan talks about (algebraic data types being one of them) but I would love to read a more balanced evaluation of the language - focusing on aspects which are rough around the edges and expected future improvements.

You’ll see more of that over the next few months. The roadmap process for rust kicks off with an end-of-the-year call for blog posts about what you want to see in Rust the next year. That invariably brings up areas where Rust could be improved, that’s kind of the point! We’re beta testing the last improvements for the year now, so looking at last year’s posts isn’t going to give you an accurate picture; a lot has happened in the last year!

That said, I can give you at least one thing where Rust needs to, and will be, improving over the next months: the async story is finally settling into place, but hasn’t settled yet. Async/await is coming and will improve things so so much. Right now things are a bit boiler-platey, and you can’t always write what would be equivalently idiomatic to the sync code. See here: http://aturon.github.io/2018/04/24/async-borrowing/

You mean like https://doc.rust-lang.org/nomicon/ ?

> This book digs into all the awful details that are necessary to understand in order to write correct Unsafe Rust programs. Due to the nature of this problem, it may lead to unleashing untold horrors that shatter your psyche into a billion infinitesimal fragments of despair.

It's surprising how many things in that book don't have anything to do with unsafe per se. It's just that, when you can't rely on the inference system to "do the right thing", you now have to actually think about subtype variance and the order that destructors run in, instead of letting the compiler worry about all that bookkeeping.

In addition to Steve's comment, I'll add one more thing.

Try writing some Rust, it will surprise you in a few ways. These are not warts (though there are some), but more features of the language. For some these features, move-by-default, single-mutable-reference, strongly-typed-errors, they will be struggles to deal with initially.

It's a language that restricts you in many ways, and this is startling for many. It was for me for sure.

Language-wise, Rust is really top-notch, I find it hard to fault any design decision. There are some design choices that annoy me (e.g., two closure types can never be equal), but there is always a fact-based rationale for why things are the way they are.

My two major pain points in Rust are (1) the compile times, (2) the high variance of quality in the ecosystem.

The compiler for Rust is very slow, it takes minutes to build software from scratch. Things are getting better with incremental compilation, but it's definitely not as fast as D or Go to compile, and that can be very grating during development.

Anyone can easily contribute to the crates ecosystem and post their libraries and programs to crates.io. Unfortunately, there is no real way to know what's production-quality and what's a toy. You can try and rely on the number of downloads, the names of the contributors, etc., but there is no system that tells you what other Rustaceans think of a crate. For instance, I tried using an HTTP client with few dependencies (because the most downloaded option, reqwest, has a lot of dependencies), but I found that (a) the library issued a read(2) syscall for each byte that it downloaded, (b) did not always return the full payload for HTTPS. There was no way I could tell from just looking at the crates.io page.

The dependency thing worries me to a fair degree. Amazon has (or at least had when I was there) a build tool fairly similar to cargo. Libraries and software was imported in to the underlying repositories with a version associated. You put that in your list of dependencies and voila when you built your code everything got neatly combined and compiled as needed.

One routine source of pain was when one of your upstream dependencies changed its dependencies. That would happen quite routinely. All was fine, unless you actually had two packages that had dependencies on different versions of a library.

You could work around it by pinning the version of the dependency, but of course that's risky. You don't know if you're exposing yourself to bugs, because you're making software run with a dependency version it hasn't been tested against.

Pretty much every build file I ever saw in Amazon had at least a half dozen or more dependencies pinned. Every now and then you'd find you were getting a new dependency conflict, and that things had become an impossible knot to untangle. All you could do is unpin _everything_ and then figure out all the version pins you needed again from scratch.

I swear I would lose at least one to two days a quarter doing that. The development focussed teams would spend way more than that on fixing conflicts.

I started out with Rust just last weekend. Put a couple of dependencies in the Cargo.toml library and got stunned when it pulled in over 400 dependencies, a number of which I'd expect to have seen in the stdlib, not left to the vagaries of random people's implementations.

For native Rust libraries this is a solved problem. Cargo finds one common compatible version of each library that satisfies requirements of all dependencies, and only when that isn't possible, it allows more than one copy of the library (and everything is strongly namespaced, so there's no conflict).

And it has a lockfile, so your dependencies won't update if you don't want them to.

The only problem is C dependencies like OpenSSL that do break their API, and don't support multiple copies per executable, so there's nothing that Rust/Cargo can do about it (you as a user can avoid them and choose native Rust libs where possible).

In Cargo you can have different libraries use different versions of the same dependency, and as long as those different versions don't interact you're fine. What this means is that if library A depends on Bv1, and C depends on Bv2, then as long as A doesn't expose something from Bv1 that you try to use with an API in C that expects Bv2, you're good.
Compile times are really a big pain point, even slower than C++, when taking advantage of incremental compilation, linking, binary libraries and experimental modules, while not being crazy with templates.

I am also looking forward to the incremental compilation improvements.

Still think that until cargo actually supports binary dependencies, the experience will not be as fast as it could be.

In our continuous integration system at work, I've enabled sccache with the S3 backend. This reduced the compile times from 20-25 minutes (tests and release build) to 8-9 minutes. Still longer than I like, but it's possible to ease the pain somewhat.
Well, that's mostly because there are almost no ugly parts in Rust. Sure, you can find minor annoyances like those:

- Type inference pretty much being killed by method calls. For instance, code like this won't work:

    fn x() -> Vec<i32> {
        let mut x = (0..3).collect();
        x.sort(); // Calling any method of Vec
        x // Cannot infer that `x` is `Vec<i32>` because a method was called
    }
- Turbofish syntax is ugly. For instance, in Rust you say `std::mem::size_of::<T>()`. It would be nice if you could replace `::<` with `<`.

- Negative modulo follows C semantics. This means `-2 % 12` is `-2`, not `10`. There is a sane implementation in `mod_euc` method, but it's not the default.

- Lexical lifetimes reject code that should be valid necessitating weird workarounds. NLL will fix that one.

- Trait objects types used to be written using a trait name like `Display`. There is a new syntax which is more clear: `dyn Display`, but for backwards compatibility reasons the old syntax is accepted.

- Macros (including procedural derives) cannot access private items in a crate, requiring workarounds like exporting private items in a crate, and having `#[doc(hidden)]` attribute to hide them from documentation.

- Trait version incompatibility issue. That one is weird, but essentially it's possible for a program to have two versions of the same library. It's possible for a library to say, have a function that requires an argument implementing `Serialize` interface from serde 0.9. If you try to implement `Serialize` interface from serde 1.0 then you will get an error message complaining about not implementing `Serialize`, despite you having implemented it, just in the wrong version of a library.

- Missing higher kinded polymorphism.

- Missing const generics.

- The compiler is slow. Like, really slow.

- The language is hard to learn because of many complicated features like ownership and borrow checking. That said, I think those features are a good thing, I'm missing those in other programming languages, but they are problematic when you are learning the programming language.

But really, there is much more I would complain about in other programming languages, so it's not that bad.

I would add:

- RC<RefCell<data>> for lambdas to access struct data, specially painful on GUI callbacks

- lack of support for binary libraries on cargo

I’d say C++ is mostly about being ugly (it’s old) whereas everything in Rust makes sense. I would advise you to check Erlang, Swift and Go (in addition to Rust) which are in my opinion these kind of languages.
Last time i tried was a year ago so things might have changed but then i think lack of GUI libraries, and libraries in general, is the ugliest part of Rust ecosystem. Language-wise it's very friendly but once you want to interact with the outside world you many times end up having to wrap existing C or C++ libraries with FFI and that's not very fun. Especially not C++ libraries as C++ doesn't have a standard ABI and can't be called straight from rust so you first have to wrap the c++ library with 'extern C' and then call that C interface from rust FFI. Also making sure the C++ lib and rust is compiled in a binary compatible way on all platforms is quite a challenge.

Good IDE support, with autocomplete and integrated debugging, was also missing but that should have improved i believe?

My thoughts on the philosophical aspects of the article don't really relate to Rust.

> These values reflect a deeper sense within me: that software can be permanent

Maybe it can be permanent, but it shouldn't be. Software is disposable and all the time developers are mistakenly fighting this aspect of its nature instead of embracing it. Great software is malleable and develops, over time, to adapt to the human that is using it. But it is impermanent--parts are snipped off here, fleshed out there, nothing stays the same, it is obsolete as soon as it is released. Don't delude yourself that your software will run for a thousand years, be like Warhol and celebrate the ephemera that is pretty much every program ever written.

> I have believed (and continue to believe) that we are living in a Golden Age of software, one that will produce artifacts that will endure for generations

Museum pieces, sure, but do we still want to be using generations-old software in the years to come? Hope not. Times change, needs change, and software that doesn't change is replaced by software that does, and quick. What about such monumental artifacts as 'cc', 'awk', or even the UNIX kernel? For years they have dominated the landscape, they are the Ozymandias, the King of Kings. If we are still clinging to these titans in another 20, 50 years, is that a good thing?

I'm glad there are Warhols doing their thing but I think the world would be worse off without Michelangelo.

awk, vim, whatever Dos 3.1 GUI the guys at B&H use to fulfill my orders, why shouldn't I expect to be using these programs in 50 years? They solve a problem and work every time.

> why shouldn't I expect to be using these programs in 50 years?

No reason at all, if they still solve the same problem, they're still the best solution. The problem with 'problems' though, is that once they're cornered they tend to change into a whole new problem. A nasty one the current solution won't work for, usually.

> do we still want to be using generations-old software in the years to come?

*BSD, vim, Emacs, Perl, C, Apache & Linux in some way, gcc, the GNU userland, Air company & banks infrastructure come to mind, and they still do the work.

There are a couple of ways to look at this. Philosophically, to pick any one of your examples, my emacs is version 26.1. Is this the same software as emacs 15.10 released April 1985? Will a perl 6 program run on perl 5? Am I the same person I was 5 years ago?

Another take: what is the ratio of the same software still in use after, say, 10 years to all software in use? I would argue that more than 99% of all software (e.g. by version number) is no longer in use after a mere 5 years. Software is inherently disposable, let's not pretend we're building bridges that will stand for generations.

My point is that developers (I'm one) have a hard time with the qualities of software: we don't understand the nature of software change, and bicker about what bumping a semver number means, and we fight its disposable nature by engineering it to the point where it could run for a decade (it won't).

(comment deleted)
I'm I the only one reading the title in Elvis voice?
Maybe Rust has passed through its dark time and come out the other side. There was a long period during which error handling was a mess. First it was too verbose. Then came "try!", which was kind of a hack to return from functions on error. Now there's "?", and a reasonable structure around how to use it.
It depends on what your field is. If you're writing any form of async code that uses futures, Rust is still firmly in the dark ages. I'm being told there is preliminary async/await support on nightly, but it won't be on stable this year.
If you're writing async code with futures, you're probably writing a web service. Go is a better fit for that.
I'm using both Go and Rust extensively. What I'm doing in Rust, I'm doing in Rust for a reason. Async code is not just for web services.
Watch the Apple - Think Different commercial at https://www.youtube.com/watch?v=cFEarBzelBs , and pay attention to the script/words. It's as if it was purpose-written for Rust.

In fact, there's even a callout to Result<> "..but the only thing you can't do, is ignore them" . :)

Jesus...
It was mostly sarcasm really — I thought the word play was fun:) kidding aside, I am intrigued by Rust. I am glad it gets the attention that it deserves.
This is great sign for Rust, as it is getting adopted by actual OSes.

All successful systems programming languages got OS adoption at a given moment on their history.

As addendum, even if off-topic, Microsoft is also making use of it on Azure IoT Edge and possibly on Azure Sphere, but the later remains to be confirmed.