Hacker News new | past | comments | ask | show | jobs | submit login
Still in love with Rust (dpc.pw)
183 points by adamnemecek 4 months ago | hide | past | web | favorite | 182 comments

> Rust will force you to be a good programmer,

> [if] you like it or not.

This is probably the best in-a-nutshell statement that describes what a good programming language is for me.

I had similar moments in the past. Before Python I cared about indention to some degree. But once I got used to the way Python forces you to indent your code, I came to the realization that this is pretty much the way I should format my code anyway and it influenced my coding style in all other block-based languages for the better.

Clojure was another language that showed me how functional programming should be done. My code in all languages changed and I used a lot less variables and a lot more constants and parameters. My code and the systems I created became a lot simpler and much more robust as a consequence. I had used Haskell before but it never really transformed my mind the way Clojure did. I also got a really good grasp on destructuring and list comprehension.

Rust offered me several relevations. I finally understood test-driven-development because the language has excellent support for it. I also understood RAII more deeply. Traits became clear to me (Clojure protocols and Haskell type classes felt strange to me). I finally understood why the Maybe monad in Haskell/ML is one of the simplest and yet bests ideas in programming.

And there is the borrow checker. I already had a functional programming mindset when I picked up Rust. But having a clear owner of data takes the mindset of functional programming even further[1], even when mutability is involved. It is a lot easier to reason about your code when you know exactly which operations can (and should) change a variable.

An aspect that the Python, Clojure and Rust communities have in common is that they emphasize idiomatic code a lot. Therefore it is much easier for newcomers to find examples of professional-level code and learn things the right way. And it is much easier to dive into a new codebase. Clojure is actually a very extreme case of this, because Clojure code tends to use a lot of 1-letter parameter names. In most programming languages you are allowed to have the variable "i" for looping over numbers. But not much more. In contrast, Clojure has a list of well-established 1-letter variables that are recognized by everyone in the community.

[1] This does not adhere to the classical definition of functional programming but I feel that it is a natural extension to it.

Only as good as Rust allows you to be, which is fine for some until they stagnate or grow out of it; but far from the final answer to anything.

I don't get at all how being forced to do anything could ever be a good thing. Smells like cognitive dissonance from here. I'm all for powerful tools that enables me to write better code faster; but being forced, really? That's the best thing about Rust? Ew.

Have you ever heard about the new thing called static typing programming language? I heard it force people to put only data with correct type into a function, it doesn't allow me to put any memory address I want to, such bad thing. Ew.

Jokes aside, I see rust borrow checker is just another dimension of type checking, it is type checking for access behavior and that's all about it.

Just as you can wrap your data in memory into static type system and leverage it to make a class of mistake impossible (passing wrong data to a function), you can wrap your access behavior into lifetime type system (converting raw pointer access behavior to reference with correct lifetime/access using unsafe) and make a class of mistake impossible.

In my definition, that's a powerful tools that help me to write better code faster.

> I don't get at all how being forced to do anything could ever be a good thing

If you don't prefer tools that can automatically check if you were a good developer or not, it not only does not scale (to trust the software you build), it's that I wouldn't want to work with you as a teammate.

> If you don't prefer tools that can automatically check if you were a good developer or not

You are pretending there are no trade offs.

> it not only does not scale (to trust the software you build)

I think we should all be able to agree that empirically that is nonsense.

I have empirically witnessed that the larger a project gets, the harder it is to trust that the code does the thing you want it to do. It's far from nonsense.

True, regardless of language.

Indeed, yet different languages seem to have different slopes.

If you only see the good, it means you are in love.

So all programming languages at a higher level than ASM are horrible to you? They don't allow you to write everything that pure ASM does. Even C enforces structured programming.

It's all black or white, right. Someone says something less than flattering about your latest ideology and the gloves come off. Consider not identifying so hard with your tools, for all our sake.

There are very few rules in C; same in Forth, Common Lisp and Perl among others; they provide tools, not religions. Python was always borderline.

These days it's like they're in some kind of competition to stuff as many rules as possible down peoples throats and get away with it. And that's not even the weird thing, the weird thing is that users are begging for more.

> Someone says something less than flattering about your latest ideology and the gloves come off.

Or, for an alternative view, someone makes an extreme statement like "I don't get at all how being forced to do anything could ever be a good thing" and when called on to explain it in common contexts you note how you are harshly being attacked because of someone's ideology.

> There are very few rules in C

There are tons of rules in C. You have to type your variables. You often have to cast between variables to change type. You've likely just internalized them and accepted them as common so you don't think of them as cumbersome.

> same in Forth, Common Lisp and Perl among others

Even Perl is opinionated in spots. Have you ever wondered why postconditionals only work on statements, and not blocks, while regular conditionals only work on blocks, and not statements? e.g.

  do_something() if $var_as_bool; # Valid
  { do_something(); } if $var_as_bool; # Invalid
  if ( $var_as_bool ) { do_something(); } # Valid
  if ( $var_as_bool ) do_something(); # Invalid
A choice was made to enhance the positive and suppress the negative aspects and possible uses of each.

The thing is, what Rust is doing with the borrow checker isn't even as subjective as that. It's enforcing a constraint which, like type constraints, is based in a mathematical understanding of how to entirely prevent certain classes of errors. Like most type systems, there are escape hatches to allow you to do what you need as long as you take responsibility. So, since it's in some aspects in concept and execution to type checking, it's natural to ask someone that is critical of it what they think of type checking, as it leads to a natural explanation of how it works and the benefits.

> And that's not even the weird thing, the weird thing is that users are begging for more.

People like street signs as well. That doesn't mean they are always followed, but it is useful to see how to work well within the system most the time.

It's not like Rust has many rules. There's unsafe, use it if you like. The rest is the minimum number of rules needed to guarantee some level of concurrency safety.

It's not like Rust has any rules just to make your code look funny.

> These days it's like they're in some kind of competition to stuff as many rules as possible down peoples throats and get away with it.

Could you expand on this please?

> And that's not even the weird thing, the weird thing is that users are begging for more.

... which users? And if the majority of them, then this perfectly explains the competition, but why are you surprised about the users' need for rules?

> I don't get at all how being forced to do anything could ever be a good thing.

So anarchy it is, then?

If those two were the only options, definitely.

But they're not.

You're statement implies there are only two options.

> I don't get at all how being forced to do anything could ever be a good thing.

You specifically say that being forced to do anything couldn't be a good thing. The only other option is for nothing to be forced.

I'd chalk it up to the old advice of "learn new programming languages that teach you something new [because you'll become a better programmer]" applying to Rust, as there is something new to most programmers in it. I remain skeptical that a total beginner starting with just Rust will be any better of a programmer (however you're measuring that) than another beginner who had started with, say, Python, giving both a year.

Python formatting is egregious though, with hopelessly long lines and no sign of where to break them up, or at the most weird places. Yes I'm one of those persons who adheres to the terminal 80 column rule, it's neat for splitting and cascades into many benefits. Python often looks like unkempt code to me, like the developer has no care for how it looks or layouts (which isn't true, it's Python's fault.) The forced indentation (I love proper formatting with consistency and indentation in languages) is among the first reasons I never liked or used Python, until recently when I gave in because it's such a great ecosystem to build scripts and such, but I still hate the formatting everyday. Clojure is beautiful though. Even C is gorgeous compared to a Python 'forced' program layout. I'll start adding Hy to the mix and touch vanilla Python as little as possible, but I still have to read library code.

> Python formatting is egregious though, with hopelessly long lines

This is the result of writing hopelessly long lines of code. Python doesn't force you to write long lines.

I've done it myself, many times, but gradually made efforts to avoid this. I'll move deeply indented code into a new function or split up a long expression with a temporary variable. If I run into a particularly hard-to-format portion of code, it's usually a sign of that my code could be better.

But list/dict comprehension puts the programmer in bad shoes, because anything interesting/powerful done with it gets dangerously long. This and other features where non-trivial statements or calls are written seem poorly thought-out, from a formatting perspective (nevermind 120 columns being the style goal for the language and common IDEs, already longish for term vim and Emacs.) Adding to it for the other commenter (vaylian) who asked for example code, pretty much any codebase I've been delving into will have at least one ugly line per non-trivial class implemented (some undergo TWO virtual linebreaks!! Maybe OK for mouse-clickers but vimmers recoil and shriek.) It just doesn't play nice. I won't go into how I dislike the class system which feels bolted-on (to a script language) too because then that'd be a bit unfair and off-topic.

Comprehensions are very easy to format spanned across multiple lines - since they're always enclosed in some kind of brackets, you can split them over multiple lines in a readable fashion:

   ys = [x + 1
         for x in xs
         if x > 0]

   ys = [
      x + 1
      for x in xs
      if x > 0

My biggest issue with list comprehensions is that while it's a useful and fairly readable format for a single list comprehension, either little attention was given to how it would function when taking a comprehension of of comprehension, or they thought the syntax would be so cumbersome people wouldn't do so (ha!).

When compared to a set of functional style mapping and filtering functions, it quickly becomes much less readable as your needs become anything more than trivial. My main issue is that focus for the important aspect of what is being accomplished swings back and forth from the front to the back of the statement multiple times, especially if nested and with conditionals. e.g.

  [(x,y) for x in range(10) for y in range(10) if y > 5 if x < 6]
or, in a similar formatting to what you show:

    for x in range(10)
      for y in range(10)
      if y > 5
    if x < 6
In both cases, the accurate reading requires scanning back and forth from beginning to end of statement (whether vertically or horizontally) because the conditionals always postfix the rest of it (and this example is not as complex as it could be). For loops would likely have the conditionals preceding everything, making it obvious, and a set of filtering statements. The functional style also allows for a fairly straightforward reading of what's going on:

  toTen.filter(y=>y>5).flatMap(y=> toTen.filter(x=>x<6).map(x=>[x,y]) );

  toTen.filter(y=>y>5).flatMap(y =>
Of course the functional style does require at least some minimal knowledge of some concepts often extraneous to novice programmers, so I understand why that wasn't chosen in Python's case. I just wish they had put conditionals in the same positional flow as the rest of the statement.

That's not quite right. The conditionals are executed in order in the innermost loop. For instance:

  >>> [(x,y) for x in range(2) for y in range(2) if print(x, y) is None if print(x, y) is None]
  0 0
  0 0
  0 1
  0 1
  1 0
  1 0
  1 1
  1 1
  [(0, 0), (0, 1), (1, 0), (1, 1)]
Both conditionals have access to x and y.

So the list comprehension is equivalent to this:

  [(x,y) for x in range(10) for y in range(10) if y > 5 and x < 6]
And could more clearly be formatted like this:

    for x in range(10)
      for y in range(10)
        if y > 5
          if x < 6
And would look something like this in a functional style, perhaps:

  toTen.flatMap(x =>
    toTen.filter(x => x<6 && y>5).map(y=>

Ah, thanks for the clarification. Python is not a language I use often (but of course is a language seen often).

I actually see what's going on a bit clearer now, as I looked closer and found that the correct way to write what I was originally trying to express is actually:

   [(x,y) for x in range(10) if x < 6 for y in range(10) if y > 5]
which could be formatted as:

    for x in range(10)
      if x < 6
        for y in range(10)
          if y > 5
Which is actually much closer to the functional style's flow, and is correctly eliminating iterations earlier in the loop (which is an important consideration).

I was confused because I had seen examples where multiple if clauses where added to the right side, one per loop level (as I showed), and that makes it look like they are operating on the different levels, when in reality they are working on the innermost loop, like you showed.

I'll retract most my complaints then. There's still some question in my mind as to how you would usefully mutate items of the loop and use them in other levels of the loop without recomputing them again, but that might just be my unfamiliarity with the construct.

I agree. The most readable syntax for sequence comprehensions that I know of is C# LINQ and XQuery FLWOR, and it's no coincidence that they put the projection clause ("select" in C#, "return" in XQuery) last - it follows the overall flow better.

Both of the examples you give are far less readable than an equivalent for loop with an if statement. Programmers use comprehensions because there is an understanding that they are more efficient and the compiler/interpreter can better optimize them.

I use comprehensions because they're self-contained. Instead of a loop with a few statements that introduces new variables into the scope, there's a single expression with fewer loose ends. I think using (non-generator) comprehensions for efficiency is usually the wrong reason.

I find the split comprehension more readable than the equivalent for loop. That's mostly because I'm used to that style, of course, but it's also because it's more constrained. Comprehensions have a very limited grammar, but there are multiple ways to write a for loop that builds a list.

They seem less readable to you because this is not what you're used to. While I agree that complex, one-line comprehensions, possibly with more comprehensions nested inside, can be quite unreadable even for experienced developers, I find that the given examples, with clearly separated expression-loop-condition structure, tend to be quite easy to read and understand, even for junior developers.

I’m quite comfortable with list comprehensions, I’ve been writing them for decades. I like them because they are concise, efficient, more clearly tell the compiler/interpreter their intent.

That said, they are almost always harder to understand than an equivalent for loop with an if statement. A list comprehension by its very nature groups a number of actions into a single expression, it’s harder to break up the parts.

Even an experienced developer who sees them all the time and can understand one in a second, would probably take a half second to understand the equivalent for/if statements.

It all depends on how you approach them. If you see comprehensions as a different form of a for loop, I would tend to agree; but if instead you see them as a different form of a map+filter combination, they suddenly become much more clear.

I agree. If you see comprehensions as map+filter, they make more sense, but it's also exactly that reason that makes them more complex.

List comprehensions are basically a watered down gateway drug to functional programming. Everyone who has ever taken a functional programming course loves it (rightly so), and often tries to find places to use it. Comprehensions do quite a bit in a single expression, it's easy to see that inch towards functional programming.

However, junior developers haven't taken a functional programming courses. They learn to program instructions or statements line-by-line. They are told (not entirely accurate) that every clock tick, the processor moves forward one unit at a time. Your mind starts to imagine the processor in that way, executing a line and going on to the next. Do this, then move forward, then do that. This is procedural programming.

A list comprehension doesn't quite fit that model, because it does quite a bit in a single line (generally map + filter, sometimes reduce). They teach you that units of complexity can generally be broken down line-by-line.

Of course list comprehensions can be formatted to multiple lines, but it is intrinsically something quite different. A list comprehension is not a statement (e.g., var foo = b + 5), it's an expression (['b' if x < 1 for x in y]) and a pretty complex expression at that.

Junior software developers are taught about statements, going line-by-line. They aren't taught about functional programming or complex expressions. I love python comprehensions, but I wish they were presented in a way that was as easy to understand as a for loop with an if statement.

Senior developers wouldn't care, but it would open up a giant world to junior devs.

What's the difference between a for-loop with an if-statement in it and the comprehension above? Even syntactically, they are almost perfectly matched on tokens. Except with a comprehension, you immediately know that not only it consumes a sequence, but it also produces one (whereas a loop could really do anything).

See my other answer to a similar question above, but basically for-loops and comprehensions are different due to their "learn-ability" and "read-ability". For-loops are easy for novice programmers to understand and functionality is broken up line-by-line as the processor consumes instructions. Comprehensions are an expression, and do several things in a single line. Novice programmers don't understand them, and I do believe that even for expert developers, they are harder to read than an equivalent for-if loop.

To me it's similar to having to break-up a long line in the middle of a non-important function call, because parens is where it's at. Feels like hooking into the middle of nowhere-don't-care just because that's the unpractical rule Python follows, since indentation is part of syntax.

There is another Python feature that saves you from this trap however - the nested function. I have lots of code that looks like this:

    def txform_item(x):
      <maybe many lines, as complex as needed, + can see vars in outer scope>
    # right below, for optimal locality of ref for human reader
    new_list = [ txform_item(item) for item in old_list ]

I follow the 80 columns rule as well. Do you have an example of such unkempt code that you can share?

Long lines come most often in conditions of if- and while-blocks, mostly because there's no way to split them over multiple lines that isn't visually hideous.

But a condition can be easily split if it is assigned to a variable, and that variable then tested. And naming said variable well can make a comment explaining the condition redundant.

Yes, that's a great example, especially with deep nesting which forced indentation makes worse. But the solution feels like working against the grain, when a language should be working for human readability, not for workarounds to make a scripting language featureful.

One would argue that forcing long conditions to be split up, and intermediate steps named, is rather encouraging readability. ~

Yes, prefer a named variable if the logic gets long. If it is really long, consider introducing a function to calculate the predicate.

Doesn't Python have an autoformatting tool like gofmt or Prettier?

Black (The uncompromising Python code formatter) is all the rage atm.


Yes. Most of the projects now use black https://github.com/ambv/black

The problem that black had last time I saw it on hacker news is that it only inserts whitespace. Unlike braces-and-semicolon languages, that is sometimes not enough to format code well. For example, given the following line:

    x[1][4] = a[2] + b[4]
Black will format like either of these:

        4] = a[2] + b[4]
    x[1][4] = a[
        2] + b[4]
But not like either of these, unless you insert the brackets yourself:

    x[1][4] = (
        a[2] + b[4])
    x[1][4] = (a[2] 
        + b[4])
Maybe this sounds like just one little problem but I think it's a fundamental flaw. I have seen the result of a Python formatter (not Black but had the same problem) applied to a couple of files and it's a total mess. I'd take inconsistent column widths over that any day. I asked the creator of black about it and he was pretty dismissive.

I use yapf and in such cases backslashes work fine:

    x[1][4] =\ 
        a[2] + b[4]

there's autopep8 and a few other tools but none are considered idiomatic.

A programming language won't make you a good programmer in the same way as a brush won't make you a good painter.

I'm not aware of any paintbrushes that refuse to apply any paint unless you hold them in a certain way. They may apply it poorly, but you're still getting some paint on that canvas.

I think a more proper tooling analogy is safety tools, like the Saw Stop. They don't guarantee that you make a good construction, but they do either prevent or significantly reduce the chance of you losing a thumb.

So you agree that a tool does not make a good programmer.

It's like the Sapir-Whorf hypothesis. The strong version is nonesense, but the weak version has some evidence. A tool cannot make you a good programmer, but it can help you be a good programmer.

OK, so a programming language won't make you a good programmer, indeed ;)

Edit: This place is disappointingly childish... It does not reflect well on Rust, either.

> Edit: This place is disappointingly childish... It does not reflect well on Rust, either.

You're getting downvoted not because of the alleged childishness of this place, but because you're trying to win a discussion on a complex issue by scoring a simplistic "gotcha".

Hmm, my original comment was not a "gotcha" it is the simple truth.

Anyone trying to sell you a tool by claiming that "it will make you a good whatever" is just trying it on. Yes, a good tool can help achieve better results but that's not the same at all.

Then, all the replies I received were seemingly defiant but all in fact agreed to the point while trying to sound smart.

The issue with your comment is the dismissive and reductionist point of view, this place isn't childish, it's exactly the opposite: people here don't tolerate so much statements of absoluteness or broken analogies.

This isn't dismissive nor reductionist. This is a simple fact of life. Perhaps experience is needed to grasp this, though, and to understand the analogy.

Childish, because I feel that some commenters see posting as a pissing contest.

I'm genuinely surprised by the reactions.

Comparing a programming language to a paint brush is reductive. And arguing "this is a simple fact of life", and that those who disagree with you lack experience, is dismissive.

>Then, all the replies I received were seemingly defiant but all in fact agreed to the point while trying to sound smart.

And where exactly is the problem? You've received exactly the type of answer you wanted. steveklabnik repeatedly agreed with you by saying that a programming language cannot make you a good programmer.

Remember, your "simple truth" is just a small part of the whole story. The other part is that less strict languages allow you get away with mistakes and be lazy but later on fail catastrophically (e. g. by "losing a thumb"). You're dismissing this very important part as "trying to sound smart" or by calling it childish.

I was not trying to disagree with you. As you said, in some sense, I agree. The point is that it's not as simple as what you're saying. There is some truth to it, but it's not the whole story.

A language (programming or otherwise) is much more than a just a simple tool. It's a way of thinking. A language that guides you toward good solutions definitely makes you a better programmer if you give it a chance.



Rust is a wonderful language, but I still have the impression that it is not stable as of 2018. All the toys I've made to play with it during the last years went deprecated quickly, especially if you rely on the ecosystem of packages (web server, database, ...).

In addition, there was this thread: https://internals.rust-lang.org/t/concerned-about-rust-2018-...

All in all, Rust will be great when stable (including the ecosystem).

What do you mean by deprecated? Unsupported dependencies? All the code I've written post-1.0 still compiles correctly (although a lot of it could use an update to use new language capabilities).

I even have a bit of Rust in production at work and I haven't encountered any maintenance issue so far.

Now the ecosystem itself can move pretty fast depending on the dependencies you use, that's true, but that's a different issue. You're never forced to update if you don't want to, you code keeps compiling with newer Rust releases. Compared to most languages I've experienced with, Rust has been very stable post-1.0. And they seem to take that issue very seriously.

The thread you linked might have some truth in it but it seems like the main point is "look, you've had a .2 patch release!" That's not a good thing of course but unless you happened to jump on that new functionality as soon as it got released the impact is rather minor and bugs happen.

> Now the ecosystem itself can move pretty fast depending on the dependencies you use, that's true, but that's a different issue.

Is it? I’m used to languages which bundle a huge set of standard libraries as part of the language.

I think it depends a bit on which in part of the ecosystem you work. I do not do web applications at all, but use Rust for machine learning. Things like ndarray, petgraph, and the Tensorflow bindings have been very stable for me.

The only large change that I had to make over the last year due to ecosystem changes was going from error-chain to failure. Of course, this was not strictly necessary, but failure seems to be more popular now and I generally prefer it over error-chain.

> depends a bit on which in part of the ecosystem you work

This is also my experience. The feeling towards Rust is highly depends on the dependency of choice.

Rust itself is mostly very stable though. I've implemented few pure algorithms in Rust a little over two years ago, and it still compile and runs today without having to change a single letter.

Wish one day I can say the same thing to my other Rust projects :)

> All the toys I've made to play with it during the last years went deprecated quickly

In which sense? That dependencies evolved?

Languages features, dependencies, etc.

For instance one project:

   error[E0010]: allocations are not allowed in constants                          
   error[E0015]: calls in constants are limited to tuple structs and tuple variants

    error: could not find native static library `brotli`, perhaps an -L flag is missing?
    error: aborting due to previous error                                           
    error: Could not compile `brotli-sys`.                                          
And I'm not talking what happens if you don't fix versions in Cargo.toml.

Allocations were never allowed in constants, so I find that first one quite confusing. Any chance you can share the code?

And the second one is because a system library is missing. You're saying you have it installed, and it used to build, but does no longer?

For first one:

    const characters: Vec<char> = vec!['7', '+', '-', '*', '/', '^', '0'];
For the second, I do have brotli installed (via brew) and I have no idea why it can't find it now. It used to build. Note that I don't use brotli directly but it comes as one of the 274 dependencies (!) of that code. npm again...

Thanks, this is very helpful.

Do you know when the first one ever compiled? I tried it on Rust 1.0, and on every version from 1.20-1.30, and it always failed.

Ah, bummer. I haven't used the brotli lib, but that is unfortunate.

"No race conditions, leaking resources, dangling pointers, unhandled exceptions, ..., the list goes on."

Except of dangling pointers, Rust has everything else from this list.

About compilation time issue: it doesn't exist anymore. Current versions do "cargo check" in a few seconds even for big projects (my biggest project is 45k LoC) and it takes a couple of minutes to compile it because of incremental compilation. For libraries it takes seconds to compile and 1-2 seconds to run "cargo check" (or clippy).

>big projects (my biggest project is 45k LoC) and it takes a couple of minutes to compile it

I don't think 45k LoC could be considered big, and I _do_ think a couple minutes compile time is a problem. I say that as a C++ programmer, a full recompile will easily get me out of the zone

Out of curiosity, not to argue: how much time it takes in C++ to compile 45k LoC?

It depends heavily on how you're counting lines of code and what C++ features you're using.

If you include a couple of C++ stdlib headers you're already well past 45kloc per compilation unit (for instance, including <vector> pulls in about 25kloc of code). Such a file usually compiles in a second or so, but the problem in C++ projects is that each source file pulls in the same headers over and over, which explodes the line count the compiler has to crunch through (I bet that in most bigger C++ projects, the actual project code is less than 5% of what the compiler actually needs to compile).

C is much more predictable, a 45kloc C project should compile and link in under a second for a full rebuild without optimizations, and at most 2..3 seconds with optimizations. The link-step is usually the critical part, since that's hard to speed up by throwing more hardware resources at it.

I have some projects of that size (ballpark), typically using some Boost (but not heavy on meta programming), Qt, and the likes. A fresh non-incremental compile usually takes less than a minute.

I have Rust projects that are many times smaller but take much more time for a non-incremental build. Of course, a large difference is that a fresh Rust build (after a cargo clean) compiles all its dependent crates, whereas many C/C++ libraries are provided pre-compiled by whatever system you are compiling on.

Well, what is the point in this comparison then? I'm not trying to say Rust is the fastest compiler on the planet, but really people don't even try to be objective.

If you're comparing, say, a 10 KLOC C++ program vs. a 10 KLOC Rust program, then the comparison is fair IMO. The end user doesn't care whether Rust chooses a different compilation strategy for dependencies.

End users run "cargo clean" really rare (I can't even name examples - really rare). If we compare regular compilation in C++, then we should take regular compilation in Rust, not something exceptional.

~100k loc takes 40 secs with `make -j 4` on my laptop.

Finally some numbers, thanks. Yes, Rust compilation is slower.

Like the others say: depends. But wasn’t saying C++ compiles faster, just that I know from experience that anything over a couple of seconds can ruin your flow.

No shit, I recently restarted a project [0] in straight C after giving it a serious try in C++. It's been a while since I dipped my toes, but a couple of weeks of decoding screens upon screens of template instantiations for every single compilation error and waiting minutes for a fresh build brings back memories. I'm not touching that madness with a ten foot pole again, they have some kind of crazy cult thing going where everyone agreed to pretend it's all good. Compiling the same thing in C is instant, I don't even want to think about how much time I wasted.

[0] https://gitlab.com/sifoo/snigl

Well, if you can't even call a range (seconds, minutes, hours) then I can say that Rust is more predictable at least :)

Couple of seconds for 45k lines - I think you are too demanding :) But if C++ can compile it in couple of seconds - great, good competition for Rust :)

That really heavily depends on the kind of code. Template metaprogramming slows down the compiler a lot.

So sometimes it takes couple of seconds, sometimes couple of hours? Maybe there's some approximate range.

C++ compile times heavily depend on the code that is being compiled and the build system that is compiling it. Code that doesn't touch STL or boost or do much metaprogramming of its own is going to compile extremely fast (especially when compiled in parallel). Code that pulls in half of boost isn't going to compile so quick without tweaking the build settings. Due to how variable compilation times are, I don't think the range you provided is that unreasonable. Build time is something that changes on a project to project basis.

It is indeed true that you can unintentionally leak resources, but it is still much harder. Also, what do you mean that it has unhandled exceptions and race conditions? Rust doesn't even have exceptions (although there are panics, which is not the same thing), and race conditions are statically prevented in safe code due to how a mutable reference works.

It's important to understand the difference between data races and race conditions. The former are a subset of the latter. Race conditions in general can happen even in single-threaded code. (Although admittedly Rust ownership semantics prevent many of the latter as well; for instance, modifying a collection while iterating over it is impossible, cf. Java `ConcurrentModificationException` which itself is thrown only on a best-effort basis.)

Theoretically they are different, in practice they are the same (it's just more difficult to handle panicking - sometimes you can't use catch_unwind because of it's trait limitations).

About race conditions: https://doc.rust-lang.org/nomicon/races.html

I feel like its quite difficult to call them the same in practice, when they serve very different roles: errors that are likely to be recoverable are not panics! in a sane rust api, whereas they very much are exceptions in sane C++. You recover from either in similar fashion, but panics! are intended to serve a much different role than exceptions (the common usage of exceptions in C++ being covered by Result<> in rust).

I hate when people use exceptions for regular errors, it triggers me hard. Please let's don't start this topic :)

You started it.... exceptions are mostly annoying because they are so verbose. Writing try... catch... is a whole lot longer than "if func() == null".

Exceptions are mostly annoying because they’re infectious, and somewhat undocumented (they’ve not part of the api; if an upstream library adds a new exception, all downstream code must now handle it [or document it]), and if anyone misses the update, its a runtime error waiting to happen. But notably, your compiler won’t say a word about it (unless its java, in which case you’ll probably get too much :-). Return codes are similar, but less cascade-y. Try-catch being verbose and fucking up control flow is just an added bonus.

Which is easier:

try: module.find_element() except: # handle not found else: # handle found

Or this:

if module.find_element(): # handle found else: # handle not found

It surely does, my Gtkmm demo application I wrote several years for "The C/C++ Users Journal" still compiles faster than the Rust rewrite in Gtk-rs, when doing fresh build or after minor changes.

Lack of binary library support on cargo, not having incremental compilation and linking does hurt.

There is incremental compilation. And cargo doesn't recompile libraries every time. Not sure how you don't know it if you really use Rust.

yeah, downvote it when you have nothing to say, that's how to make a discussion.


Incremental compilation from 1.24 stable: https://blog.rust-lang.org/2018/02/15/Rust-1.24.html

Cargo recompiles dependencies only after "cargo clean" or after Rust version update.

I didn't downvote you.

Cargo surely recompiles common dependencies across crates, unless one uses the workspace trick and even then recompiles do happen.

Just get Gtk-rs, get nightly and then trace the build log. I once even mailed Rust devs about it.

Incremental compilation still isn't up to incremental compilation + incremental linking, which is why I explicitly mentioned both on my comment.

To clarify two things you've said:

1. Hacker News won't let you downvote someone who replies to you, so it is literally impossible for you to have downvoted in this situation.

2. Both of you are correct, because you're talking about different things. Evgeniy means "You only need to compile your dependencies upon the first build", and you mean "if I use the same dependency in two projects, it will be compiled twice, once on the initial compile of both."

Thanks for the clarification.

No problem. Others haven't had this conversation as many times as you and I have :)

Yep, I still look forward to eventually advocate the contrary.

Even when I sometimes sound negative, I really appreciate your work.

Rust achievements thus far already influenced design decisions in other languages.

> And cargo doesn't recompile libraries every time.

Given how often Rust updates, it might as well.

“Bad programmers worry about the code. Good programmers worry about data structures and their relationships.”

I think this hits the nail on the head for me: abstract structure first, the choice of language C/C++/Rust/... is then a secondary consideration.

I don't think that holds, given the potential scale and severity of memory safety bugs in systems programming.

I'm not saying Rust is the right answer but not paying attention to, or worse, putting our heads in the sand, when it comes to our tools is not the answer.

let me tell you that Rust forces you to very carefully consider data structures and their relationships.

The point of a programming tool is to help the programmer to be a better programmer, there is indeed no point using a very strict language, statically typed with lot of constraints attached for a perfect programmer.

But for commoners it would certainly help to have some guiding.

Which brings up the question when/why to pick Rust? There are a plethora of languages which from my layman perspective seem more interesting. OTOH there are a plethora of languages with rich ecosystems, proven pedigree and with a high demand for such developers.

I am working on medical devices, where a bug can have catastrophic consequences.

Essentially the ecosystem today is C and C++ for embedded FW and middleware. Even if we are careful, we rely on static analysis tools to avoid some categories of bug. (Plus a humongous amount of tests). Typically bugs that would be caught (mostly) by the Rust compiler at compile time. I think it is a great idea to have some safety guarantee at compile time.

Would Rust fixes every problem ? Absolutely not, you'll always get logical bug or implementation not conform to the requirements but I think it is a move in the right direction, and I hope that slowly in next 10 years Rust will grow in the embedded space.

And overall cargo is nice, C/C++ don't have standard package manager. The test infrastructure is there by default, this is a breeze to add them in your code, rather than relying on an external framework, so this is also good.

I don't really see an alternative today for small footprint SW where C and C++ is king, and I hope that embedded toolchain vendors are taking notes.

If today you mostly code in javascript or python, unless you need bare metal like performance or to have stronger safety guarantee, maybe it does not make sense to move to Rust.

Sure, nobody would choose Rust over C++ if you couldn't express pretty much the same data structures and relationships in Rust that you can express in C++.

I'd still love to see a good tutorial on setting up Vim to work well with rls and all the other plugins. I only really keep VSCode around since it works with rls and sometimes I want to use the autocomplete for codebases that are large enough to really need good autocomplete, but not so large that rls gets bogged down and doesn't work at all anymore.

For language server autocompletion, install coc.nvim - https://github.com/neoclide/coc.nvim


  :CocInstall coc-rls
and should work out of the box.

I also use https://github.com/w0rp/ale to provide linting. Also works out of the box.

You can get these things running on vim8, but honestly it's easier to just use neovim.

If it's of any use, you can check out my full config at https://github.com/tom-james-watson/dotfiles.

Does vim-racer[0] not work for you? I don't exactly know what you mean by "good autocomplete", but if you are more specific, then maybe we can help you a little more? :-)

FWIW, my Rust setup in vim is mostly the following plugins:

* NERDCommenter, for commenting stuff

* rust.vim, for general rust things (like integration with rustfmt, playground, and the next two plugins)

* tagbar, for showing the list of tags (of the ctags-esque sense) in a sidebar.

* syntastic, for on-the-go syntax checking.

[0]: https://github.com/racer-rust/vim-racer

Idea worked wonderfully for me with tiny pet projects.

Same. Rust needs a one-click install "integrated de"

The comment about "no race conditions" is not accurate. Rust guarantees an absence of data races which are not the same as race conditions.


> Barriers to entry

So far I had two faltering attempts to learn Rust but somehow I didn't find good enough material to get me hooked.

I am looking for something similar to the Tour of Go[1], where you can interactively try the language by solving minimal tasks. Does someone know of such material?

[1]: https://tour.golang.org

That looks pretty good. Thanks :-)

I found The Book [1] to be pretty good, but it suffers the same problem that I find infuriating about programming language books in general. They have to target one specific proficiency level, so someone less experienced is going to get left behind, and someone more experienced is going to get bored out of their mind.

As a somewhat experienced Rustacean, my biggest frustration as of now is the lack of a comprehensive reference where I can look up very specific things (e.g. the syntax for byte string literals, or an exact definition of how borrowing works in some particular edge case). There is [2] but as it says on the first page it's incomplete.

[1] https://doc.rust-lang.org/book/2018-edition/index.html

[2] https://doc.rust-lang.org/reference/introduction.html

I come from C. What got me started was the O'Reilly book, Programming Rust by Blandy & Orendorff. (It cultivates the your sense of snobbery too. Imagine code examples using lists of "murderous Renaissance artists".)

In fact, that was the book I started with once. I kept reading until page 76. The book isn't bad, but I think books are just not the best medium to learn new languages, at least for me.

> Let's face it – typically developers are familiar with OOP, garbage collected, dynamic programming languages.

I think most of these programmers, and their project are not the target of Rust? If you're writing Java/C# or higher level languages, you already made the choice to sacrifice some computing efficiency for programming efficiency.

I always thought Rust was more destined to convert C and C++ (and D/Swift/Go ?) programmers, that need the raw power and control.

We have three major audiences:

* "systems programmers", aka the C and C++ folk

* "functional programmers", largely Haskell folk

* "scripting programmers", mostly Ruby/Python/JavaScript folk

Each has gotten something out of Rust, and also brought some things to Rust.

You're missing the group which Java, C#, Swift, D and Go co-inhabit. Somewhere between "systems" and "scripting".

I did say "major" for a reason :)

I don't personally hear from many self-identified Java/C# people who are coming to Rust. Maybe they exist and I don't hear from them, or they don't describe themselves as such.

There are some Go developers, but it seems the languages attract very different types of people, so there aren't many. There are some people who do love both.

There are only a few people from D (of which technically if you squint I'm sort of one, incidentally.)

> I think most of these programmers, and their project are not the target of Rust? If you're writing Java/C# or higher level languages, you already made the choice to sacrifice some computing efficiency for programming efficiency.

Or you just don't know any better, as was the case for me.

There are many benefits to Rust that are not related to runtime efficiency.

I originally picked up Rust because I couldn't make audio processing in JS fast enough.

I stayed because I found Rust to be a language that not only doesn't get in your way, but also helps you out of some situations.

I mean, I had the compiler say to me "Perhaps you could [...]". Perhaps. And nine out ten times it was a good suggestion.

I don't know the demographics/targets, but I come to rust from ruby. So does Steve Kabnick, the author of the Rust book.


What kind of projects do you work on, and what made you switch?

Well, I have not "switched". For now I am just learning (at a very slow pace).

My current job is not in ruby any more, now I do Lua - I'm a core dev at Kong - https://konghq.com/ . It's much lower-level than ruby, but still not as low as you can go with rust.

Thank you for this piece. I am working on a game, that I am prototyping in Go. In my prototype, I have just entered the realm of multiple threads, and for this project to work out, it needs to handle quite a lot of threads working on the same resources.

While I have not personally dappled this far into concurrency in Go myself, I have read plenty of articles where developers recommend using the mutex package rather than the goroutines themselves.

But I've also read about Rust's thread and resource handling -- and I'll admit, I've been looking for an excuse to write some Rust again -- and so inspired by this entry, I've decided to finish the prototype in Go, and build the real version in Rust.

There was a great talk recently about how to properly build an entity component framework in Rust if you haven't seen it yet.


Thank you for that link. Very useful. It already highlighted a solution in Rust for a problem that I've run into with my Go prototype. And I appreciate there also was a blog post to read.[0] I personally prefer articles over videos.

[0] https://kyren.github.io/2018/09/14/rustconf-talk.html

Can someone familiar with both Rust and F# (or OCaml) type systems write few words about the expressiveness aspect of the language?

What would I miss in Rust if coming from the F# background?

In the surface you can say yes or no (only looking at types and such). But summing all the features, F# is far more immediately productive.

I take rust after 2 years on F# (that I continue to use). I have like 2 months of Rust now. I still struggle to solve things that are "no-brainers" in F# or swift (mainly, because Rust is VERY strict about know statically all about the code, and VERY hard to build generic, dynamic code).

So, it will look less expresive, IMHO. I still ramping-up and start to get more a more of the rust style of programming, so the pain is reduce. With the additions for rust in the near future it will reduce it more, I hope.

Fun fact: Rust was originally implemented in OCaml, and takes a lot of inspiration from it. Rust was originally described to me by someone as "C meets OCaml."

The biggest thing missing in Rust that are in both are higher kinded types. The biggest thing missing from OCaml is parameterized modules.

F# doesn't actually have HKTs either, unfortunately.

I thought it did, my mistake!

Native async/await support is really great. I haven't gotten very far with Rust but it already made me rethink many things that I thought I had understood.

I tried Rust a few weeks ago. Is my understanding correct that Rust doesn't have the equivalents to the following C++ features yet?

- non-type and template template parameters

- constexpr functions

I understand that Rust macros can replace them some of the time, and they are much better then the C preprocessor, but that's quite a low bar to meet. Last time I checked non-type template parameters (called "const generics" in Rust) is on the roadmap, so it is promising.

The equivalent of "constexpr functions" has a design, and is usable in nightly.

The equivalent of "non-type template parameters" has a design, but is not yet implemented. It's being worked on.

There's no direct design for the equivalent of "template template parameters", but a similar feature has a design, but is not yet implemented. It's being worked on.

A `const fn` RFC is accepted and somwhat available when using nightly compilers, also see https://github.com/rust-lang/rust/issues/24111

Another point against Rust is binary size.

If you write the Fibonacci generator in one of the first chapters of the Rust book it will create a 4MB binary. Even stripped it's over 400KB.

If you write the same code in Nim it compiles down to 112KB with debug symbols, unstripped. Statically linked against libc (still with debug symbols) it's only 850KB.

850KB statically linked versus ~450KB dynamically linked and stripped.

For code that is virtually identical.

Rust binaries include jemalloc. However, jemalloc is removed in nightly and by default Rust will use the system allocator:


This should reduce binary sizes quite a bit.

And some rough numbers, rustc-1.30.1 vs rustc-1.32.0-2018-11-21, building a hello world with --release...

Mac OS:

- 1.30.1 before strip: 573K, after strip: 380K

- 1.32.0 before strip: 267K, after strip: 178K

Ubuntu 18.04 LTS:

- 1.30.1 before strip: 3.9M, after strip: 407K

- 1.32.0 before strip: 2.3M, after strip: 191K

I'm on Void Linux right now running Nim 0.19 compiling the following code:

    when isMainModule:
      echo "Hello World"
nim c hello.nim:

Before strip: 113k

After strip: 92k

nim c -d:release hello.nim:

Before strip: 79k

After strip: 63k

nim c -d:release --opt:size hello.nim:

Before strip: 46k

After strip: 30k

Kind of a toy example but it illustrates my point. An unstripped version of the Nim program with full debug symbols is still 78k smaller than the stripped, release version of the Rust equivalent. But it's great that they're trying to work on this problem.

>But it's great that they're trying to work on this problem.

I don't know how the Rust team thinks, but I believe that minimizing binary sizes is not a very big priority for them. The cost of a gigabyte these days is, ~0.01$ (or less) for local hard drives, $0.4 for NVME and about $0.1 for AWS EBS...

Technically of course it's cool how small Nim binaries are, and it does make certain things feasible which with larger binary sizes might be difficult.

We think it's important to be able to control it if you need to, and have done work to make that possible. But we don't worry about the default too much, as you say, we've got a lot of things to worry about, and it's not near the top of the list.

That said, the smallest executable produced is 145 bytes: https://github.com/tormol/tiny-rust-executable

The difference gets bigger on large binaries.

How far does this scale? What are the binary sizes of a 100k loc app in Rust vs Nim?

See reply from kungtotte

Does this matter anywhere outside of embedded software nowadays?

Yes: security sensitive applications where being able to inspect or fuzz a binary is strongly dependent on size. Also firmware on servers.

Rust has really strong fuzzing support, incidentally https://github.com/rust-fuzz/cargo-fuzz

So does C and other languages.

The moment a programmer conflates OOP with 'complicated graphs with lots of interdependencies between nodes', it's the moment I stop reading their writings.

OOP does not force one write complicated graphs of objects. OOP has its applicability, FP has its applicability and well-written code in conventional languages is a mixture of those two.

> Now I see how before Rust I was often writing my code in a typical OOP-way: forming complicated graphs, with many objects referencing each other, with unclear relationships. The class APIs, inheritance hierarchies, were there, but they were just obstructing the core problem: structuring and manipulating data... Rust will force you to be a good programmer, you like it or not.

Rust today demands static knowledge of all relationships. But class APIs, inheritance hierarchies, and the like are powerful tools for dealing with dynamic relationships: code that runs together yet evolves separately, where Rust is feeble. Good programmers recognize that different problems require different solutions!

Rust is feeble at patterns it wasn't made for (OOP), absolutely. :). Composition over inheritance.

Rust core development is working on many important features in parallel. The feature I have been most excited about, and wish was here already, is async/await. I wrote quite a bit of asyncio using futures and it was a bloody, exhausting experience. Lack of documentation or examples made the experience even more challenging. Much of what I wrote could be trivially refactored to async/await syntax, but I may keep some around to honor hard-earned gains. It's not clear to me yet whether the first stable release in Q1 2019 will allow me to port everything but I look forward to finding out.

dpc, the author, is such a helpful, positive force at the #rust-beginners IRC channel

Rust still lacks an effect system, though. Any plans?

No current plans.

Bad programmers worry about the code. Good programmers worry about data structures and their relationships.

Mildly horse shit. It takes both on a team.

How does data-centric work? How is it different from OOP? Can someone elaborate for a person using python?

Also, what opinion rust devs have over golang?

> How does data-centric work? How is it different from OOP?

Rust doens't have objects. You have structs, which are purely data, and functions, which are purely behavior. OOP puts the two together. So in Rust, you don't tend to design things by saying "what are the objects I need?" but rather "what is the data I am manipulating and how do I manipulate it?"

> Also, what opinion rust devs have over golang?

I am not 100% sure what you're asking here, could you maybe re-phrase? Are you asking why Rust is better than Go?

Oh nice, good to know.

> Are you asking why Rust is better than Go?

No, just the opinion in general, why rust over go? I know go is more oriented to distributed systems, but it seems like rust eventually will be used for that as well. And I also see golang being used to build just anything, like gopass for example.

Ah. That's a more interesting question, for sure. :)

I got involved with Rust over Go because if I'm going to use a statically typed language, I need generics. That was the initial thing, but other things ended up going similar ways (I think package management is extremely important, for example.)

I think Go has made great choices to accomplish its goals, but I'm personally more interested in a different set of tradeoffs.

Rust sounds like the dream. I gotta check it out sometime

I was going to try out Rust, then I saw this on the download page

  curl https://sh.rustup.rs -sSf | sh


The script is less than 400 lines, and is pretty easy to audit. We wrote it in such a way to prevent some issues like "if the download gets cut off only part of the script gets run." It's served over HTTPS.

You can also probably get an install from whatever package manager you use, if you prefer that.

You can detect whether the script is being piped into sh, so downloading the script via for example FF might produce different results than piping it to shell, making audits useless.

Sure. If you're worried about that, it's easy enough to download it first, and then run it, without the pipe.

If you're auditing it, you've already downloaded it, so it's unclear to me why you'd re-download and pipe rather than just running it.

You still have zero guarantee that other people are getting the same code.

Encouraging people to run code from some random URL on the Internet is always bad.

I've been looking at Rust recently, and my conclusion is that it is not production-ready.

It seems like an ever-changing work in progress at the moment, with idiosyncrasies such as this.

Can't have a Rust post without a mention of D.

Am I the only one who really can't stand rust?

I was and still am very much in favor of the ideas and concepts, but when I actually tried to use it my opinion on it turned by 180°:

- The syntax is awful - No dynamic libraries - Slow compile times - Many checks are too stupid to figure out some valid cases (maybe that improved meanwhile)

You are not.

- No dynamic libraries

This is technically incorrect, but practically correct, by the way.

> (maybe that improved meanwhile)

The next version, released on December 6th, ships "non-lexical lifetimes", which does make the borrow checker smarter.

> No dynamic libraries

While I understand that some people prefer this, I prefer static linking to dynamic linking. So much in fact that I refuse to use environments that only do dynamic linking in any of my projects.

Yes, I have to make a few concessions to that because of the idiocy of glibc, but other than that.

I have really tried to like it. Coming from OCaml, it has many of the things I’ve come to expect in a modern language, but with better tooling and more libraries.

I generally do not mind syntax. I like OCaml’s and Erlang’s, for example, which are usually considered ugly, but for some reason I can’t enjoy the look of Rust code. I think all the nested <<<>>> and the use of :: are a large part of this.

But the real issue for me was refactoring owned struct fields into borrowed ones. The ‘a lifetimes leak everywhere; you have to add them to the struct field, after its “impls”, and everywhere the struct name appears.

I’ve found that Pony provides the same level of safety in a much more lightweight manner with its capabilities system. Interestingly, if I’m not mistaken, early Rust was somewhat similar to Pony.

Unfortunately Pony doesn’t have the same development resources and thus the ecosystem is somewhat lacking.

No. I wanted to like it, but a lot of stuff (standard type names, syntax) is different just for the sake of being different.

I wish D would have won the C++-successor race.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact