7 ms·
Comparing Rust and JavaScript Ergonomics with a Simple Linked List
- jerf 8y agoHeh, I feel like "someone" ought to write the Big List of Bad First Projects for This Language, e.g.: Go/Erlang/Elixir: "Testing" the concurrency by parallelizing the addition of an array of integers via some sort of sending single integers over messages, or in the worst cases, literally spawning entire processes/goroutines/etc. to add two ints (and then wondering why the concurrent program fully consumes all eight CPUs but is still twenty+ times slower). Go (as of this writing): Immediately trying to implement a generic data structure. Rust: Complicated pointer-type data structures like linked lists, or goodness-forbid, doubly-linked lists. Haskell: Starting right off by trying to implement an in-place sorting algorithm. Python: Taking your C numeric algorithm and converting it into pure Python (and then being shocked at the performance). Criteria for my inclusion is that I personally have seen each of these many times. It's not that these tasks are necessarily impossible, or even necessarily all that hard when you know what you are doing, just that they are bad first tasks, but they seem to tempt people for some reason. For example, someone reading a Python tutorial hasn't heard of numpy, if you're just learning Rust immediately learning how to break the rules you don't fully understand yet isn't the best use of your time, etc. In this particular case it all turned out OK in the end, but it often doesn't go this well. :)
- stcredzero 8y agoIt's not that these tasks are necessarily impossible, or even necessarily all that hard when you know what you are doing, just that they are bad first tasks, We programmers are supposed to be intelligent, but somehow we seem to be easily focused on micro-details, to the point where we start to rant and fail to pay attention to the whole. There are some people who read car reviews and just want to know the horsepower. There are other people who read car reviews and want to know about the ergonomics, visibility, handling, ride, comfort, noise, features, reliability, fuel economy, etc... There is nothing at the level of complexity of a programming environment that is devoid of trade-offs. There are going to be hundreds if not thousands of trade-offs, reoccurring many times over a long span of time, across many different people, in an ever varying environment of changing requirements. And yet, there are many things that are like "reviews" of programming languages where there is only a micro focus on a few features. They're like car reviews where they just quote 0-60 times and horsepower stats. Relevant: https://xkcd.com/356/ https://xkcd.com/356/ (Is "Blub" best thought of as analogous to "Corolla?")
- ergothus 8y agoI can see real value in such a Big List of Bad First Projects - why not toss what you have on github/gitlab/etc and ask for contributions? The explanations as to why those are Bad First Projects can be very informative.
- matthewaveryusa 8y agoAnd what's your bad first project for javascript? I'm curious because I think javscript, despite the bad parts, is on average pretty forgiving. my two cents for C++: -Anything where you revert back to C-style memory-management and wonder why the code is so complex/segfaulty
- apendleton 8y agoFor JS: straight-forward batch processing. Open this CSV, iterate over the data, munge it in some way, spit it out again as a new CSV. This kind of thing is like a 4-line Python script but in JS/Node requires readline or some other kind of standard library thing, nest-y callbacks and/or promises, or maybe async and understanding how that works (which, for a brand new dev... have fun with that), etc. If you need to do IO but don't have a task that benefits from async, things in JS tend to feel unnecessarily torturous.
- jdlshore 8y agoIt's not so bad. const fs = require("fs"); const filename = process.argv[2]; const fileContents = fs.readFileSync(filename, "utf8"); const output = munge(fileContents); process.stdout.write(output); It's worse if you want to use async IO, mostly because the ecosystem is still catching up to async/await. But with a bit of boilerplate or a willingness to use experimental APIs, it's also not bad: // This API is still experimental const fs = require("fs").promises; // This will become unnecessary when top-level await is supported run().catch((err) => console.error(err)); async function run() { const filename = process.argv[2]; const fileContents = await fs.readFile(filename, "utf8"); const output = munge(fileContents); process.stdout.write(output); } My Node.js projects' tooling is all written in Node.js and I enjoy it.
- apendleton 8y agoThe specific situation I've encountered is that it's a file that's bigger than I want to hold in memory and I want to read it and process it a line at a time, but I don't want to do anything fancy or threaded or concurrent. Read a line, do something to it, write a line. Bog-standard ETL stuff. And yeah, async/await helps, but is unambiguously ergonomically worse than: import csv incsv = csv.reader(open('file1.csv')) outcsv = csv.writer(open('file2.csv', 'w')) for row in incsv: outcsv.writerow([row[0], row[1] + ' blah']) and also just requires understanding a lot more stuff before you can be productive if you're new to the language. I'm not saying it's not possible to do it in JS, just that it's not a task that plays to JS's ergonomic strengths, just like it's possible to write a linked list in Rust but kind of sucks.
- kbp 8y ago> Complicated pointer-type data structures like linked lists If a singly linked list is a complicated data structure then what's a simple one?
- nickpsecurity 8y agoAn article you might find helpful is this one on why imperative algorithms are harder to mathematically verify than functional ones: https://semantic-domain.blogspot.com/2018/04/are-functional-programs-easier-to.html https://semantic-domain.blogspot.com/2018/04/are-functional-... You don't need to be a mathematician to follow it with this excerpt summarizing its key point: "The difficulty of imperative programming arises from the combination of state, aliasing and procedure calls. Any two of these features can be handled without that much difficulty, but the combination of all three effectively makes reasoning (nearly) as difficult as correct concurrent programming. " The garbage-collected languages let you ignore the aliasing problems with a performance penalty. Rust in safe mode without GC forces you to (a) deal with it and (b) deal with it in a way that works for all inputs (type/memory safety). That usually requires mathematical verification using tools such as separation logic. Rust's method is much easier to use with tradeoff of limitations on expressing code in certain ways. Folks that get along with the borrow-checker say it helps to design the program around data and easy methods of using it versus starting with control flow forcing it on a data structure. I'll also add that linked lists are inherently hard to get right on all inputs regardless of low-level language. They're actually a popular way to test new, mathematical methods for specifying and proving algorithms correct. Fortunately, Rust folks have a goto write-up on linked lists in their language: https://cglab.ca/~abeinges/blah/too-many-lists/book/ https://cglab.ca/~abeinges/blah/too-many-lists/book/
- staticassertion 8y agoThere aren't really simple data structures that involve pointers. Pointers are just hard (where hard means they require programmers to hold state in their head), and rust makes that explicit. The reality is that implementing data structures that require pointers is not a typical task, and they're ideally provided by the stdlib or crates.
- tetromino_ 8y ago
- Scarbutt 8y agoFound the JS implementation horrible, he could just have use plain objects (would be fair since he used a plain struct in rust) and functions instead of 'this' everywhere.
- codesections 8y agoI'd be interested to see what you have in mind/hear why you think that would be a big improvement on the way I had it set up. (I'll admit to not putting a ton of thought into the JS version, though)
- pornel 8y agoOh no, anything but the linked list. Real-world Rust programs don't implement linked lists. There's LinkedList in libstd if you really wanted one, but in modern architectures cache locality is so important that almost always some other data structure is a better choice. Rust's standard library and crates.io have plenty of containers to choose from. Linked list is popular because it's a CS101 topic, and because C is so barren. When you don't have any containers in stdlib, dependency management is hell, and due to lack of generics any 3rd party implementation will be either inefficient or full of macros, only then low-effort hand-rolled linked list seems like a sensible choice.
- deleted 8y ago[deleted]
- monocasa 8y agoLinked lists get shit on a lot, but they're great for a lot of use cases that Rust is ideal for. Hell, doubly linked lists are pretty much the data structure of the Linux kernel, and it seems to run pretty fast.
- steveklabnik 8y agoBrian Cantrell has some really interesting blog posts on this http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performance-of-c-and-rust/ http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performa... Sometimes, choices aren't entirely about the structure itself, but about the affordances that a language offers you.
- gmueckl 8y agoI recently had to write a bunch of custom linked list like structures in C++. Seeing how Rust adds that big heap of complicated boiler plate on top of the actual algorithms doesn't exactly make me a fan of that language.
- baq 8y agono double delete and no dangling pointers is the tradeoff. i once spent two 12h long days hunting a dangling pointer. it was fun finding it, that one time.
- codezero 8y agoI'd like to see this comparison, but with TypeScript instead of JavaScript. I'm sure it will still be better than Rust, but it seems like the main thing making Rust really awkward here is the memory safety.
- cryptonector 8y ago> What's more, according to that book, there simply isn't a good way to implement this structure in safe Rust. The way to go is to venture into unsafe Rust. Noooo, you don't want to do that. Just give up on circular references, doubly-linked lists, and so on. Use different data structures and work around the [perceived] downsides of not having the data structures you're used to in Lisp, C, ECMAScript, ... You'll find it pays off. See this HN post for more: https://news.ycombinator.com/item?id=18098239 https://news.ycombinator.com/item?id=18098239
- simplify 8y agoCan someone explain why raw_tail was necessary in the add_to_tail function?
- codesections 8y agoWe needed to use a raw pointer there because of Rust's ownership system. Here's the short version: Rust wants everything to have exactly one owner and enforces that by disallowing multiple mutable references to the same data (or, for that matter, multiple references to the data if even one of the references is mutable). But that doesn't play too well with linked lists. You need to have a reference to the tail node from the previous node and have a reference from the list itself. And you need to be able to mutate the tail node, so you can't just do that with immutable references. Now, rust does have some safe ways to solve that type of problem (Arc and RefCells are two). But it turns out that the best way to solve it here is to make the jump to `unsafe` code since we can guarantee the invariants that keep it from being unsafe in practice.
- desireco42 8y agoIf I had a dollar each time I needed to use and implement single linked list... (I wouldn't have many dollars)
- codesections 8y agoYeah, I 100% agree that they're not good examples of the sort of problem you'll need to solve in a given language. That said, I still like them—at least enough to write this one, anyway!—because they're small, self-contained, and dive deep enough into the language internals that you can (start to) see the differences in the "world view" of the language. (Compare them to something like Advent of Code challenges—those are also small, self-contained, and unrealistic to real programming, which makes them pretty similar. But many of the early ones don't get deep enough into the language to really get a feel for how the language pushes you to think/approach problems)
- Shebanator 8y agoI'm baffled how the author could look at these code samples and say that the Rust and Javascript versions were mostly identical.
- offbytwo 8y ago>Rust is pretty >Of course, you might feel differently, but one of my biggest takeaways from all of this side-by-side code is that Rust is clear, expressive As someone who doesn't use either of these languages, I couldn't disagree more. The Javascript was intuitive and immediately made sense, while the Rust code gave me a headache.