Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Rust from 0 to 80% for JavaScript Developers (michaelsalim.co.uk)
129 points by michaelsalim on May 1, 2022 | hide | past | favorite | 59 comments


Some small corrections:

> It’s an error handling magic for async functions

The question mark operator is unrelated to async - you might say "fallible functions" instead.

> This indicates that it’s a macro. Basically a shortcut.

I don't think "shortcut" has a specific meaning that is related to macros in either Javascript or Rust.

> An implementation of trait. So class (?).

Not really a class. There isn't a good parallel to this in most other languages. It is the link between a trait and a type, and provides a specific implementation of the trait.

> Unlike JS, Rust has a type for the result of the promise/future called Result

The `Result` type is not related to futures. Any function can return a `Result`, and a future can yield any type (not necessarily a `Result`, although it is commonly used in that way).

> The Result enum has Ok and Err. If the future is successful, it returns Ok, otherwise Err.

Again, no relation between `Result` and futures.

> Use this when you’re confident that it won’t error.

Personally I would recommend never using `unwrap` outside of tests. If you want to panic on the `Err` case, then `expect("...")` allows you to provide an explanation for why the `Err` case should not have been possible.


> The question mark operator is unrelated to async - you might say "fallible functions" instead.

This jumped out at me too and I think it would be better stated as “this function doesn’t handle this error” or “this function may produce this error”, which is basically checked exceptions ~without error handling as arbitrary control flow.

> Not really a class. There isn't a good parallel to this in most other languages.

It’s a lot more like a class than the next most obvious TypeScript idiom (structural type compatibility/duck typing). It has to be explicitly defined and that’s the most important part coming from a JS/TS background.

I agree with and fully endorse the rest of this comment.


> which is basically checked exceptions ~without error handling as arbitrary control flow.

With the `?` operator, Rust behaves identically to a language with checked exceptions as it also has "arbitrary control flow" in that the code may "jump" elsewhere on errors. The implementation may be different, but the abstraction is identical.


The places it may jump are explicitly marked in the code, though. And that makes a tremendous difference in readability. The problem with exceptions is not the alternative control flow, but the fact it is hidden, and an exception can come out of nowhere, from 10 layers below (that's particularly bad for unchecked where you can't see them even in signatures).


I really don’t think it is that much of a problem. You almost always want a high-level error handling logic, and not all errors can or should be handled at callsite. Rust’s ? macro is a good tradeoff, but I really do think that exceptions are the best way of error handling — optionally marking a given exception type as “should be handled” aka checked exceptions. Though existing implementations are not without errors.


You responded while I was typing but your response is much better.


I genuinely don’t think this is right! The syntax is equivalent to try->catch->throw or whatever but it doesn’t change the fact that the function has a clear either/Result type. Every call site gets the same return and has the same error semantics whether that syntax is used or an explicit return statement/expression is used. ? just means it’s a potential early return. Not a goto.


I don't know rust, so pardon my lack of knowledge. But given a quick look, can't "traits" be seen just like "typeclasses" in haskell?

Btw, I feel like I should learn rust now.


Rust's traits are a limited version of Haskell's type classes. They are more limited in at least two ways:

1. Rust's traits are unary relations (i.e., predicates) on types, whereas Haskell's type classes support relations of arbitrary arity ("multi-parameter type classes").

2. Rust's traits do not support higher kinds, so you can't define many things which are considered pretty basic to Haskell programmers (Functor, Monad, etc.).

But despite these limitations, Rust's traits are still great and better than what 99% of other languages have.


Yes, Rust traits are Haskell typeclasses. What he's saying has no parallel in most other languages are the `impl` blocks (which can be used to both define arbitrary methods and implement traits, the latter being directly analogous to Haskell's `instance` definitions).


Not just methods, impl defines all associated functions for a type, and Rust automatically allows you to call any associated function as a method if its first argument is some form of "self".

If you've got a Cat named puss, the call puss.meow() is effectively shorthand for something like Cat::meow(&puss) where that first parameter was the &self parameter to your "method".

Because this is namespaced, there's no problem if your Cat type has this meow() method and a Trait (say, AnimalNoises) it implements also has a meow() function, Rust knows those are different functions and the confusion only arises for a human reader, and only in code that actually wants to call one of the two identically named functions.

AnimalNoises::meow(&puss) and Cat::meow(&puss) are distinct, but writing thing.meow() requires inspecting other nearby code to discover whether from context this code knows thing is a Cat or just wants to make AnimalNoises. Where this might be confusing you should write AnimalNoises::meow(&thing) to make your intention clear to humans, and have it checked by the compiler.

Only the type's author gets to implement it, and only once (the standard library and especially core are special) so there's an essential distinction between SomeType::function() which was necessarily provided by the SomeType author, and just third party code that works with this type.


There can be additional differences in call syntax due to Deref support, which can delegate methods from a "child" to a "parent" type. For example, if we have puss: Rc<Cat> then puss.clone(); will implicitly call Cat::clone and return a new owned Cat (which can add overhead and is not zero cost) , whilst Rc::clone(puss) will simply create a new Rc reference to the same Cat object.


> For example, if we have puss: Rc<Cat> then puss.clone(); will implicitly call Cat::clone

[Edited, this used to suggest it's a typo, but probably it's just a different way to think about what's happening, so I removed this claim]. There almost certainly isn't a Cat::clone and in fact what's happening is that Clone is in the prelude, so this is:

Clone::clone(&puss)

[edited to add]

Which is really even:

<Cat as Clone>::clone(&puss)

and because Rc is a Smart Pointer it implements DeRef and so that's ultimately:

<Cat as Clone>::clone(<Rc as Deref>::deref(&puss))

Definitely understandable that people want to write puss.clone() instead.

By the way, misfortunate::Multiplicity demonstrates Deref and DerefMut nicely, providing a type which has two things inside it but references to it act like one thing when being mutated and like the other when merely referenced...

Not something for absolute beginners, but worth seeing to build correct intuitions (and to underscore Rust's admonition not to implement Deref and DerefMut if you aren't a smart pointer -- misfortunate is like Jackass, this is for entertainment and not to be attempted in your real Rust projects).


Extensions in swift feel mostly the same to me, except you can define them on any type you want and not just types or traits your namespace defines


You are correct. I know nothing about how Rust was actually developed, but I'd bet a lot of money that its creators were very big into Haskell. In addition to traits/type classes, Rust has quasi-monads (the Try trait is a fraction of the functionality but ends up being 95% of the real world use IMO) and uses a weaker form of Hindley-Milner that, again, is good enough for 95% of inference. And of course it has sum types and a limited set if Haskell's pattern matching (with the @ syntax to boot).


It was actually much more heavily inspired by OCaml than Haskell early on. See this comment talking about it, which includes a quote from Graydon on the topic: https://news.ycombinator.com/item?id=24956301

But it’s true that much of the inspiration for Rust comes from functional languages, and the trait system is definitely inspired by Haskell.


They are exactly typeclasses.


You seem knowledgeable about Rust, is this article pointing in good directions to learn the language from a dev primarily coding in JS?


As someone only passingly familiar with Rust and coming from a JS/TS background I agree this article is not helpful. If you want to make the jump:

- if a good article would be useful to you, the best resource will almost definitely be The Rust Book which is just a really well organized and easily accessible website

- if you prefer just exploring, Rust and its ecosystem is a lot easier to just wander into and try stuff than JS; but it’s going to feel really strict and have unfamiliar rules even if you’re used to strict TS

- even if you bail out of either it’s worth the time, I guarantee you’ll learn something even if it doesn’t show its value right away


No. The article is terrible (sorry). The OP was probably generous with "some small corrections". Not only the article touches up on some minor details about the language (mostly syntax) but gets them wrong or with a mistake half the time. These details are hardly 10% of the journey into Rust.

The best place to start is still the Rust book: https://doc.rust-lang.org/book/ and then start coding with it. I'd also suggest you learn some C along the way, if you only have a JS background.


Do let me know the mistakes and I will fix them!

And agreed, the Rust book is excellent.

I did have experiences with C which made going into Rust that much easier. But for others that don't, wouldn't you think going straight to Rust would be easier than learning C on the side?


I've recently started learning Rust with very little experience in low level languages. Just go build something and read the Rust book: https://doc.rust-lang.org/book/title-page.html.

The trickiest thing is ownership and lifetimes, but that's not really something you can understand just by reading. You need to build an intuition for how ownership and lifetimes work.


My recommendation for people learning rust for years has been to read both the Rust Programming Language book and Programming Rust at the same time.


Thank you so much! I've corrected them on the article.


Rust is one rabbit hole I really, really regret going down. Put weeks of work into it, got frustrated nearly every day, wrote copious notes, re-read things, and the end result is... I still can't use it for anything gnarly (ie everthing, because why else would I use a systems programming language if I wasn't doing funky stuff with memory?)

Rust has great PR but I found it to be a profound waste of time. YMMV.


I think Rust gets much easier when you understand it's the most or close to the most opinionated language ever made. If something is hard or feels really awkward to express it means you're "doing it wrong". A lot of classic solutions to problems that you've learned over your career are hard or impossible for Rust to validate. When you run into these hurdles you're supposed to switch to different style/solution rather then trying to fit a round peg in a square hole.


Yeah, this. Particularly when you come from a language that is heavy on using references like JS, Python or Java (and probably a good majority of languages with GC), you might get initially hard time with Rust. After I started appreciating pass-by-move, accepted occasional cloning and used references mostly for temporary, short-term borrows, suddenly it all clicked for me. The upside is that this way the code is actually much simpler than a spaghetti of cyclic references I got used to in big Java projects.


Personally, I've had the opposite experience. Rust is one of the few languages that has proper support for algebraic data types and pattern matching, which I consider table stakes for a programming language. I know that's not what draws most people to Rust, and I'm also drawn to many other aspects of it, but it bothers me that most languages embrace product types but have little or no support for their categorical dual.


I tried picking it up recently. Not for any real reason, just curiosity. I didn't get terribly far, but found a lot of frustration. I then watched a YouTube video where a guy was saying how even though he knew and liked Rust he still got frustrated a lot by it and found that developing in Go was much more productive. I dropped Rust and started experimenting with Go because I began to get afraid that I wouldn't get past the unwieldy stage with Rust.


> because why else would I use a systems programming language if I wasn't doing funky stuff with memory?

We use Rust for regular boring old backend servers at work. Not for speed or efficiency either. Rust doesn't need to be just a hot mess of memory manipulation (and you're never going to learn it like that)

If you want to learn it, don't do the weird stuff first, it'll just be confusing


> why else would I use a systems programming language if I wasn't doing funky stuff with memory?

Actually quite the opposite. You would appreciate Rust in applications that manage non-memory resources. RAII is a breath of fresh air compared to all those try-with-resources / AutoCloseable hacks.


Chances are you don’t need Rust then. I think the only fail of Rust is that it markets to a much wider audience then needed. It is a low-level language, period. Sure, it is one of the most handy and expressive low level languages, but if your program doesn’t have to sit too close to the hardware than the litany of managed languages will be just good enough. GCs are plenty fast so the slight tradeoff is entirely worth itz


I was much more productive with C++20. Finding a known bug in the borrow checker (people were telling me what I wrote should have compiled) was the last straw for me.


I’m not sure what “gnarly” things you tried to implement, but for anyone else in a similar situation, a good starter usecase for Rust is writing a command line utility.

Crates to check out:

- clap for command and argument parsing

- anyhow for “easy” error handling

- log + env_logger for logging

- reqwest for HTTP requests (start off by using the blocking client)

- serde + serde_json for handling JSON

- rayon to “magically” parallelize your code (using multiple threads)


Exactly, Rust's sweet spot are kernels, drivers, firmware, GPGPU and everything else where using a language with automatic memory management isn't an option.


What's an alternative?


Zig or Nim maybe


Zig is way more low level. Nim is a great alternative though. 90% of the performance with 50% of the work.


There is no more low-level. Rust and zig can express the exact same programs. Rust just catches a few more erroneous ones at compile time, but you have to manage memory explicitly in both.


Zig doesn't even have a string type. It's not an alternative to Rust, which is meant as a safer C++.

Nim has a huge stdlib.


Do any of them have a wasm target?


Nim can target js directly, but if you really want wasm it's mostly probably possible https://forum.nim-lang.org/t/7365


Can someone compare this post to mine (1)? I made a highly similar post 2 months ago and got just 2 upvotes and no comments.

[1]: https://news.ycombinator.com/item?id=30869137


I simply didn't see that one (and only got around to reading the OP now). I like your write-up better, though


There are a few too many mistakes in this to recommend it as a learning resource as-is. If you check back later maybe the author will have some things fixed.


I've corrected some of them based on the feedback here. If you spot more, let me know and I'll fix them :)


Website renders poorly on mobile (Android at least).


Same on iOS


> Use Cargo.toml instead of package.json. You’ll want to add them manually (instead of using a command like yarn add)

The cargo-edit tool gives you yarn-like commands to edit your packages. The main command, "cargo add", will be integrated into mainline cargo next update (I think).

https://github.com/killercup/cargo-edit


Oh this is good to know, thanks!


Fantastically helpful article, imperfections notwithstanding. Many thanks!


[flagged]


Um, yes. I've been trying to talk the "async" crowd down from making libraries async only. Rust has real threads and real concurrency. Both work, but mixing them gets complicated, although it's still safe. Async gets in the way with highly parallel programs.

As I've said before, while I mostly write Rust at this point, I'd suggest Go for server side web stuff. GC won't kill you there, and it's a simpler language with a more user friendly concurrency model. The neat thing about goroutines is that they cover the use case for both async and threads. On the Go side, most of the libraries needed for web stuff come from Google and were written for their internal use, so they're of reasonably high quality and well-exercised. What we don't need in the Rust crate library are a large number of crates which do roughly the same thing in different ways, with different bugs.

It takes forever to clean up stuff like that. A decade ago, I noted that Python had standard library functions for generating RFC 3339 timestamps ("2012-09-09T18:00:00-07:00") but no standard parsing function for them. There were five different packages with parsing functions for that format, each with different bugs. So I suggested standardizing this.[1] After six years of intense bikeshedding, it was finally put in standard "datetime" in 2018.

[1] https://github.com/python/cpython/issues/60077


> Um, yes. I've been trying to talk the "async" crowd down from making libraries async only.

There's no such thing as "async only" in Rust. You can always run tasks synchronously using block_on. Rust uses a principled approach to resolve the "function color" issue for async.


What an ignorant statement. Do you think you are somehow better than the people contributing to the JS ecosystem? How does this kind of thinking benefit anyone at all?


It's is admittedly quite the arrogant statement yes. But my point is that the best practices in library development in NPM and crate are very, very different and when I look at NPM and having to pull in hundreds and hundreds of dependencies, all of which are brittle, I don't desire an ounce of that thinking in crate-land.


It's likely because it is an order of magnitude harder to use Rust.


I see. Yeah I agree with the “hundreds and hundreds” of dependencies part.


The good thing about Rust, is that even for small programs, you'd have to consciously think about the design and understand a good bit about the language to have a functioning program. You could b.s. your way a little bit, but you'll quickly get caught up in an ownership or type error.


Haters gonna hate but the truth is that it transpire everywhere in the design of Rust that it was designed mostly by programmers prone to ivory tower/purity thinking over pragmatism, as a consequence the language is one of those with the highest cognitive load/overhead and for the wrong reasons. Rust is in many aspects a major progress over C++ (a goal actually quite trivial to achieve, in restrospect). The concept of zero cost abstractions are nice marketing, except when they're not. Rust has some design constraints in order to maximize performance like C++. However, because of the collaborative and pure thinking nature of the project, many non-needed for performance choice have been made, to the down of cognitive load and therefore the net result being a loss of programmer joy and intelligence (since cognitive resources are a limited budget). I am aware that rust has reached a point of religion on HN so feel free to censor my sound arguments.

Examples: * Immutable by default with a verbose syntax (let mut..), compare this with Kotlin val to var. It appear clear that rewritability has not been well thought, if thought at all. This basically means writing variables is a pain, your thinking process is constantly stopped by making things no longer.. constants.

Options. yeah null is so impure. Except you now have done the worst thing you could have ever done to a language, wrapped types... The unergonomy and verbosity is strong. When you see Typescript, Kotlin, C# and Dart non nullable types and smart casts you clearly realize Rust has made a permanent historical accident here. If you don't know what I'm referring, it only cost one google search.

Results, same problem as options. Verbose, thought interrupting. The cognitive noise of polluting return types is strong, very potent. If they wanted pure thinking nazism they could have added optionally or not, exceptions on the type signature, like java checked exceptions, or the incoming exception signatures in typescript. The number of rust error handling libraries is a testimony to this failure. When such core features are lacking, a language is a failure.

Throwing away object oriented programming is the cringiest mistake of rust though. Because it's not cool enough in 2022. Rustc was first designed in Ocaml, with all the cognitive biases this imply. If composition is that nice well make it a proper pattern like Kotlin delegation, except rust did not. And even if delegation was ergonomic, inheritance still is the most sensible approach both in terms of semantics (yes not everything is a has, hypernyms matters) and in terms of features. OOP allow encapsulation which is essential, better code reuse , visibility control, contracts and overriding. Rust do not have constructors so the method to instanciate a resource is conventional.. All of that because people have seen too much memes on twitter about Java factorySingletonDank which are indeed non-representative about reasonable code in the wild. see also concrete examples: https://medium.com/hackernoon/why-im-dropping-rust-fd1c32986...

bonuses: String types hell

no named parameters in 2022, do they fail to realize denoting and accessing semantics is the most important thing in programming? That means in many context, rust code is much less understandable.

no unified async runtime..

There are other omissions, such as no overloading, this post is non-exhaustive.

That you disagree or not with some or all of my quantifiers is a thing but you have at least to agree that there is a real market for a better C++ that would be non-hipster and pragmatic with a goal of maximizing code clarity and the developper cognitive bandwith while retaining runtime performance. That better C++ would be C#/Kotlin-like but leverage LLVM and have no-GC.

A testimony from a previously rust fanboy that has following rustc development since its pre-1.0.

Edit yes flagging me is a good sign validating the belief of HN being an effective echo chamber that force-align allowed thoughts and beliefs and amplify them.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: