Rust Objectives Observation

Well, as for myself, I always liked simple languages like LISP, C, PROLOG, and some of the functional ones. I never really developed a taste for the full-blown statically typed, hand-holding, object-oriented, etc. programming. Though I did dabble in Java and it put me off that style even more.

I can accept on a theoretical level that it can "catch some of your mistakes" but I am not yet convinced that the amount of extra effort needed to write all that complex guidance for the compiler to be able to do so is a positive trade-off, compared to debugging the few rare run time errors I might get without it. Of course, dynamic typing requires a different style of programming discipline: writing and incrementally testing small units. At the moment, before noticing any benefits, I am far more likely to be making ten times as many syntax errors. So I am experiencing a bit of a cognitive overload and culture shock :wink:

I always considered the greatest strength of static typing to be the run-time efficiency, not the handholding of the programmer.

Hah! Well then I spoke somewhat backwards to your point, and withdraw even the implied historical caveat about simplicity / cognitive load. The cognitive load I was mentioning some concern about is only that which arises from relying on implicit work done by the compiler to simplify the textual interaction with the type system (eg. inference of captures, lifetimes, types, derefs). I'm worried sometimes it winds up doing something a little too automatically, doesn't make you spell things out in full.

(In other words, I wish it had retained a little more cumbersome ceremony around its type system! As for "handholding", I'm afraid "catching errors at compile time" always been a primary focus of the project, and most everyone involved would strongly object to any simplifications that reduced its thoroughness at that.)

8 Likes

As another undercover Ada fan, I have to object to this language being brought up as the antithesis to Haskell and C++'s complexity.

Ada certainly did many things right, but simplicity wasn't on the list. The modern Ada Reference Manual, though very readable if you learned the language from the right textbooks (Ben Ari and Barnes for me), is anything but short and simple :slight_smile:

2 Likes

@graydon Sure, I understand. You have got to do what you have got to do. The question I have posed is whether lots of people can genuinely get to enjoy programming like this. Every successful language needs real enthusiasts.

I think the point is to learn some lessons from Ada/Whiley/ATS2/etc and develop them into usable, sexy, succinct, syntax&semantics :slight_smile: Ada is too much verbose.

2 Likes

Certainly, it takes a certain mindset to like strongly typed languages whose goal is to have the compiler catch more bugs for you at the cost of explaining more things to him in code.

But for the specific kinds of bugs which Rust takes aim at, namely all the undefined behaviour in C like memory errors and data races, you really want all the help that you can get, because standard debugging tools will be of very limited help in the face of those, once you've sorted out all the easy stuff that will just segfault reliably.

3 Likes

Very interesting to hear from so many different backgrounds :smiley:
I'm hoping the general conversation in this thread will help me get a firmer grasp on all the tradeoffs being discussed. Thanks @rustafarian for starting this, I'm learning a lot! :slight_smile:

I'd be quite interested if you could comment on that a bit more;
For closures I can understand how they're "too expressive", because a lot of magic happens in capturing the state. It isn't immediately obvious which vars are captured and which aren't, and then I'm not even starting on mutability and moving. That takes digging into the closure's innards (unlike fn, which make this very explicit in their signature, which I'm starting to appreciate). Is that what you meant?
As for Typeclasses (which became Traits in Rust, if I understand the jargon correctly) being too expressive, I'm probably missing something, because I don't see how that is "too much" magic...
I come from a java-ish background, with some python, perl and arduino mixed in. I really like abstracting over "thing that behaves like a some_interface. What am I missing that makes such a thing "too expressive"?

My experience after a few months actually writing of Rust is that "that extra guidance" is something that would have been dangling-pointer/use-after-free/obscure-race-condition material in C (based on my own mistakes, and those I've seen in other beginners, though that may be a biased sample), or otherwise performance-eating over-reliance on the garbage-collector in java-like languages. So in effect, things that I should have been worrying about in other languages as well.
I guess the opposing view there would be that such attention to detail only really matters to the hot loops, but rust is 'forcing' us to pay that excruciating amount of attention everywhere, even for non-essential cold-paths.
My own counter-opposing view (so far) is that this amount of attention leads to tighter code all around, more considerate programming in the entire code-base, and (hopefully) less bit-rotting dank corners of the codebase five years from now.

This seems like a worthwhile trade-off to me, because I've worked on a large commercial C# codebase for pharmacies. All throughout the day, the webserver logs of that application collected unhandled exceptions that nobody knew where they came from, had no idea what they did, which page was even crashing, and that no customer ever complained about. These errors where so consistent, that we effectively got worried when they were absent, because it meant website-usage was down. Now, when errors are starting to become "expected behaviour", that should be a wake-up call to do some soul-searching.
Everyone on the team I asked about them said they don't like them, but nobody had managed to pin them down so far, even after days of debugging.

On another project, a fortran codebase from the early eighties, still in active development in 2004, we ended up adding "unit tests" that checked every 24 hours: "Does this public function still return the same value as yesterday?". That's the best we could do with the manpower we had, because there was no way to extract the program into modules that could be sanely reasoned about.

That "we don't know what it does or how it does it, but it seems to work, let's move on" attitude has me deeply worried for the profession of software development in general. Add cve.mitre.org to the mix, as an excellent source of how "sloppiness" is a real danger to everyone, and I'm simply ecstatic that enough like-minded people came together to create Rust.

I'm right there with you :smiley: :wink: I'm learning so much about other languages by learning Rust, that I'm starting to wonder if this is that "enlightenment" that so far only the Lisp-people seem to obtain.

hear hear!

For "inspiring enthusiasm": My brother (game engine developer) has warned me that my gushing about Rust sounded borderline zealoteous, and threatened to put him off of Rust forever because no language could be that good. (I've tried to become more moderate since then :wink:).
Also: Friends of Rust seems to indicate that there's at least a healthy niche where good performance and great reliability is required, specifically dropbox and OVH's logging platform.

2 Likes

It's been said before (perhaps not in this thread, although I skimmed and may have missed it) that Rust is frontloaded on the learning curve, whereas other languages are the opposite (or somewhere in the middle). It makes you understand quite a bit before the compiler accepts your code. Other languages let you get an executable binary with less fuss, but then push the learning curve to the runtime (i.e. mistakes are detected there, sometimes in very vague and hard to debug ways).

As for type safety, it really starts to shine with a big and long-lived codebases (that see maintenance and human churn). If you encode the domain properly and use the type system appropriately (this is a skill itself but the language has to allow for it) you can save yourself some runtime headache by having compiler catch errors - think of it like a really picky and detail-oriented peer doing a code review.

3 Likes

The thing I found most disorienting when starting out with Rust is the lack of type ascriptions in canonical Rust code. I like to browse "well regarded" code of a language X when learning it, and this was a bit difficult due to heavy use of type inference; when you don't know the basic/common types and their APIs, couple that with understanding how Rust type system itself works (including things like generic lifetimes, trait lifetime bounds, variance/subtyping, etc), "hidden" autoderefs sprinkled through, closure capture rules, and so on, it all looks chaotic at first. So, this isn't the language to compliment one's learning efforts by looking at existing well-written Rust code too early. It's best to really start from the basics and write some (basic) code yourself to solidify the material as the curve gets steeper. At some point, after looking at enough Rust code and playing around, it all starts becoming more familiar and the fog dissipates (maybe not entirely, but headed in that direction).

Edit: I should also mention that it helps to understand what problems Rust is solving, particularly with the borrow/ownership/lifetimes system. It frames the context, and might lead to better understanding of why things are the way they are. That's not to say there aren't quirks (e.g. lexical lifetimes is oft mentioned in this regard) along the way, but I feel like there aren't that many, certainly nothing too distracting (once you've faced them and know how to work around) or insurmountable (or at least I haven't found that yet).

5 Likes

I've seen the kind of black magic used to keep certain large (3+ millions of lines) commercial C programs sufficiently bug-free, and it feels harder to learn and use than most user-level Rust code. What matters is not just the complexity of the language, but the sum of the complexity of the language and the large program you have written.

6 Likes

Yes, and the efforts to make the language more ergonomic have a risk of adding even more magic to Rust. There's often a tension between having a language handy to write and a language easy to read & understand for the persons that haven't written the code.

2 Likes

Right, it's a delicate balancing act between newbie and expert (or at least well-versed) friendliness. One way to look at it is that you'll be a newbie briefly (for some definition of briefly), but be well-versed for a longer period of time. Assuming one believes that, you'd want to lean more towards expert (I'll use this term somewhat loosely) friendliness. The trick, then, is to make sure there's enough guidance/docs/help/etc to let motivated people get over that initial hump, rather than abandon ship.

I will say that despite the magic, compiler will typically hold your hand if you don't quite get the magic as you're making changes. This is much better than some other languages where the code will go to runtime, and then the magic explodes in its full glory (if you're lucky - if not, your code will just silently be doing the wrong thing).

5 Likes

While this discussion about how to make rust better and more approachable is valuable in and of itself, I think you should be aware that it's really unrelated to the question of getting wide usage. C did not get wide usage because of it's "efficiency and simplicity". In the early days - before C was portable, before ANSI made the language much more complicated and less efficient, before += was an operator - C wasn't generally considered an approachable language. I was told that thinking it would be suitable as a first language was crazy.

It's been said that C rode on the coattails of Unix, but that's not quite right. C - as an efficient, portable "high level assembler" made porting Unix so cheap that hardware vendors could buy a license and port it for a lot less than it cost to license a proprietary OS or develop their own. That's not the only thing that made Unix succeed, but that's not relevant here. What's relevant is that C became widespread because it was tied to a killer application. Again, not the only reason, but I think we've got the others (inexpensive quality implementation, community, documentation) covered.

Ditto for Perl, which replaced Shell/Awk/Sed scripts for sysadmin work before becoming popular for applications on web servers even though it wasn't really suited to such at the time. Python was mostly web apps with a large helping of analysis thrown in (notebooks, etc). Javascript spread with Web 2.0.

So if your primary concern is improving popularity (personally, I think popularity should take back seat to many things), then the crucial question isn't how you make it easier to use, it's what are people going to use it to do?

Personally, I think embedded systems are the answer for Rust. IoT is clearly coming, and the bugs that Rust is shines at finding - the memory and timing issues already mentioned here - are the ones that get devices pwned. Running on bare metal on the design goals. And is there another language that can really compete with C in that arena?

4 Likes

It seems also other beginners run into this. That one seems to have be bitten by the difference between T and &mut T being camouflaged by type inference and runs into lifetime issues because of it.
I'm starting to see how this can be confusing people, especially since most dynamic languages let you write virtually the exact same code, and the garbage collector just magically solves it for you.

That's exactly my experience; I started lurking rust and rust-code long before I wrote it myself. When I started actually writing my own extremely basic exercises, I was really surprised how much compiler errors I was running into. Fortunately I'd also read enough community content to have learned that everyone seems to have that experience, and regarded it as my own personal trial-by-fire. It was very satisfying each time to solve the error by learning why it was happening, although I'l admit to random sprinkling of &, mut and prayers to Ferris too.

Exactly! I've said it before: Writing Rust may be harder than writing C, but debugging (large) C is way harder than writing large Rust.
Once a program starts exceeding one "mind-sized chunk" of complexity, where in C your brain may (just) have been able to keep track of all the pointer-juggling, then Rust's stricter guarantees start to really shine. I can't wait to experience that effect in a "legacy" codebase that has been written and maintained by a changing team 5 years before you even join the company.

In that context: I love that "unhelpful compiler warning" is considered an actual bug by the compiler team.

Hmm, interesting observation! Both for Rust and the other languages. I was personally expecting it to be high-performance server farms, but that arena has more competition, and you are definitely correct that IoT pairs even nicer to Rust's strengths.
Now that you mention it... I've seen the company particle, who make IoT chips like the Spark, Photon and Electron, explore Rust for exactly these reasons.
There also seem to be quite a few blogs on "how to write a kernel in Rust", and @japaric was being awesome in helping @rotschopf get up to speed in embedded development. I wouldn't be surprised if there's also a company behind there.

Edit: And why am I not surprised to learn that @japaric is also operating on the intersection of my two examples, by writing Rust firmware for the Photon.

2 Likes

Just to come back to one point which Graydon made, but my cellphone-based reading missed...

You'll note the language didn't have closures at first, much less type classes! This was because I literally think they are too expressive for their own good -- too much magic is implied by writing one -- which detracts from comprehensibility.

As someone doing C++ dev at work, I do feel quite uneasy about the implied capture magic in Rust's closures. To me, like C++'s catch-all captures (& and =), they're a great recipe for making code less understandable (what did this closure just do with my scope?) and for doing the wrong thing by default (whenever threads are involved, which is my #1 use case for closures, I always end up having to tell Rust to move explicitly without the implied magic understanding that it is necessary on its own).

I would probably prefer a more wordy, but more understandable explicit capture scheme which lets me say what I want to capture, and whether I want a closure to capture scope by move or by reference, on a per-binding basis. It could be optional, like explicit capture is in C++, but it would certainly allow me to get rid of quite a few workaround. To me implied capture is too much magic... and worse, magic which fails too often to be useful.

I sort of have the same feeling, but the fact that compiler will not allow misuse helps - in C++ for example, if you specify the wrong capture semantics, and the value isn't live by the time the lambda is invoked, you'll segfault or worse. Rust is a bit too implicit here, but I think eventually one can internalize the rules for most cases. The rule of thumb is Rust prefers references/borrows for captured values, but if closure ends up consuming the value, it's moved.

1 Like

Oooooohhhh yep, this is exactly my experience with production Rails apps. "Undefined method on nil" errors, and other errors, in production all day every day, totally normal. And there was never enough time or reason to dig into them, and people weren't complaining about them, but they were happening!! Every time I have to type a little more to make a value an Option in Rust, and add some handling of Some and None, I'm incredibly grateful that it means I won't have those kinds of mysterious errors in production.

12 Likes

The main innovation to me seems to be in assignments (including function parameters assignments) transferring unique ownership and pointers not being allowed to change pointed to values without explicit type annotation.

Would you agree that this is a fair summation? If yes, why not emphasise this more upfront to aid learning?
If no, what other important innovative principles am I missing?

(Of course, there is also carefully thought out syntax around this but that just kind of follows and most of it exists in similar forms in other languages, too.)

I don't mean to trivialise, I am just trying to understand what are the main unique principles first.

There's quite a bit more to Rust than this. Just for a few examples:

  • All references must obey reader-writer-lock like semantics. This makes multi-threaded code a lot easier to write, and makes it easier to reason even about single-threaded code using references (spooky action at a distance from other side of the codebase is forbidden).
  • Trait-based generics avoid both the incomprehensible compile-time or run-time errors of duck-typed generic code, and the performance cost of dynamic type erasure. In short, they make generic code both efficient and pleasant to write.
  • Rust borrows lots of useful patterns from functional programming that are not typically found in imperative languages, e.g. immutability by default, pattern matching, closures, enum-based error handling... all that without losing the performance benefits of mutability or making imperative code a PITA to write.
  • A well thought-out type inference system avoids cluttering the code with type annotations while keeping interface contracts strong (unlike in dynamic languages, you always know what a function expects as input and what it will emit as output).
6 Likes

I have not found Rust difficult to learn. I am familiar with all of the concepts applied in Rust from other languages (C++, Scala, C#) and it's internals, except borrow checker. Although, it can be also counted as known to some extent concept of static analysis for variable's lifetimes and 'single at a time' mutation owner, albeit Rust is game changer in the quality and reliability of this analysis.

What can make an expressive language hard is ability to read and interpret it's expressiveness easily using only source code. Remember Perl, which was named by someone as write-only language :slight_smile:

So far so good, I only started few days ago. The main corner stones so far are difficulty to find 'implicitly' applied converters (see more here Why Box<Future<...>> is type equivalent to just Future<...>) and understand where lifetime of a variable comes from and how to control it (i.e. best design practices on expanding it's lifetime, except using Rc<> or Gc<> every time).

3 Likes