From Julia to Rust (2021)
miguelraz.github.io
miguelraz.github.io
I have no idea why this blog is making the rounds again and I've learned a lot of Rust since, see my (badly benchmarked!) presentation at this year's Scientific Computing in Rust 2024:
https://www.youtube.com/watch?v=bdsBhqKOe7A
and a PR I sent in for the Rust compiler to speed it up for some low hanging fruit:
https://github.com/rust-lang/rust/pull/110477
I'll have to revisit this blog at some point and see what I should update, as well as a "From Rust to Julia" post for those that want to venture into that world as well.
[1]: https://github.com/miguelraz/miguelraz.github.io/blob/master...
[2]: https://github.com/miguelraz/miguelraz.github.io/commits/mas...
Other thoughts: Developing Rust on Windows is painful but on Linux a joy. both cargo and Julia package managers are wonderful compared to other language alternatives.
* Rust's tooling lets you have a base-floor of what is acceptable code that is much more palatable than non-top tier Julia codes. There's a world of difference opening a random crate's code in Rust that has been linted/clippy'd/check'd by the compiler rather than a random Julia package that a coworker/colleague just fired up. This tooling is getting better and better in Julia and I'd be interested to hear which Julia projects you worked on were really hampered by TTFX and refactoring woes. Hard agree on the footguns - I want the VSCode plugin to be better at detecting dead code, it cost me weeks on a project lately.
* I'm also excited for some notion of interfaces to come to Julia. Perhaps it will be a 2.0 thing, but there's still lots of design stuff to figure out.
* We have different understandings of "productive" for different focii then - I still find it painful to do the equivalent of `rand(1:10, (20, 20))` in Rust and I can get STEM people to be productive with Julia before you can even finishing saying "borrowchecker".
Have you checked the TTFX in 1.9+? It is significantly reduced for various packages e.g. Plots.jl.
The nicest way to avoid the "oh a lot of boilerplate!"-problem with implementing "Add" for a custom type is by using the "derive-more"-crate. Then you simply put a "#[derive(Add)" above your struct. That was the initial reason I switched to Rust myself: because I was writing a dynamical systems solver in C++ and had to overload arithmetic operators for my state-struct. But doing this was so complicated and I never felt sure of it performance wise (what if one side is a reference? And so on).
In rust I have a macro and don't have to ever worry about that again.
Oh, that's a neat crate, I'll check it out.
I'd be interested to see if you have benchmarks of your dynamical systems solver in Rust vs the ones implemented in Julia. They've done great work!
Typo: where -> were
That's not really the case. For loops are certainly less common compared to C, but they are completely fine to use. Forcing every loop into an iterator-chain style can sometimes be more confusing.
Not to mention doing for-iterations with early exits. Yes, you can use try_fold, but if I'm not writing generic iterator library code, I'd prefer explicit for in cases like this.
> writing Fortran-ey code instead of iterators will lead to pain and slower loops.
No it won't. At least not in the general case. Not to mention that iterators also have performance footguns that are pretty easy to trigger.
There are no C style "for" loops in Rust: https://doc.rust-lang.org/reference/expressions/loop-expr.ht...
There is the "for" expression, but that does not give you a C-style "for" loop. It gives you something akin to a "foreach" you might see in Java, C#, Go, etc.
> A for expression extracts values from an iterator, looping until the iterator is empty.
They look like this:
for i in 1..10 {
println!("{i}");
}
And they're totally fine to use in Rust, I agree that the article is not exactly accurate here.EDIT: Okay you edited your comment to say this doesn't count. Sure. This is what the author of the post was talking about, though.
>>That's not really the case. For loops are certainly less common compared to C, but they are completely fine to use. Forcing every loop into an iterator-chain style can quickly become an unreadable mess.
Implies a C-style for loop, some sort of counting accumulator, and an update expression. That doesn't exist in rust. That's all I was saying.
I know who you are, so I know I'm preaching to the preacher.
> For loops are not idiomatic in Rust - writing Fortran-ey code instead of iterators will lead to pain and slower loops. Spending time reading the examples in the iterator docs and the community solutions in the exercisms will help a lot.
I think they are talking about C style for loops or Fortran style do loops, they explicitly contrast against iterators.
Edit: Although, I wonder, what does an iterator like you’ve shown compile down to? I’d imagine Rust is smart/modern enough and that iterator is common enough that the assembly emitted looks a lot like a c for loop. Or better.
(0..10).iter().for_each(|x| println!("{x}"))
over the thing that I wrote.Many folks do not think of Rust's for loops as using iterators under the hood, and so will often talk about them as though they don't use iterators, even though they technically do.
When I wrote this, my main takeaways were numerical computations with arrays, which don't tend to use for loops in Rust, and which require grokking iterators for maximum performance and rayon-parallelism boons.
To a Julia audience, seeing this style of iterator chaining is quite a new flavor to think about composing computation, so I wanted it was with that audience in mind that I wrote this.
Yeah I think that makes sense! I don't do that kind of work and so it's more usual in the kinds of code I write.
Since the .next() implementation of std::ops::Range is essentially an i++, you'll quickly arrive at a similar representation as classic C for-loop.
All the other syntax, including both "for" and "while" loops and the related but technically different "while let" loop, while useful to humans to communicate intent, aren't semantically different - and so they're transformed early in compilation, they are just "syntax sugar" and are de-sugared into rather elaborate loops with their exit condition now a loop break condition etc.
But yes, as in C++ and similar languages, the iterators generate the same machine code as you'd have gotten for writing the C-style for loop from the 1970s, it's just that in Rust you literally can't write that loop.
Hypothetically if you do
for i in 1..10 {
println!("{i}");
}
(From earlier) the compiler should have enough information to fully unroll the loop if it feels like it. So it is a different syntax from the programmer’s point of view. Maybe it goes through some “this is a loop” stage inside the compiler. But then hypothetically the assembly output could… end up not even having a loop at all (fully unrolled, no jump back, haha). {
let result = match IntoIterator::into_iter(1..10) {
mut iter => loop {
let next;
match iter.next() {
Some(val) => next = val,
None => break,
};
let i = next;
let () = { println!("{i}"); };
},
};
result
}
(95% sure I got that correct)And so in some sense, "loop" is more primitive than "for".
> But then hypothetically the assembly output could… end up not even having a loop at all
Absolutely, with any of these loops, the compiler may not emit a loop in assembly. And in this case, that seems to be true: https://godbolt.org/z/7Gq69Gvre
for i in range(10):
print(i)
consumes 1 value at a time from the `range(10)` generator. `for` has no special knowledge of numbers or ranges or any of that.It's especially odd to point out this language detail, because for expressions always return the unit type. So it's not like you can do something with the return value (unlike loop expression, which can evaluate to one of its break expressions).
It’s the reason for crates like itertools.
I don’t think that is pedantic at all.
The author was contrasting loops - an imperative construct (which both C-style for and iterator-based for are examples of) - to the functional style of chaining iterator functions, adapters & reductions and passing closures around.
FWIW I agree with you, it's just that I was trying to make a distinction between imperative and functional style in Rust (just like the author was trying to do) - not literally claiming Rust has C-style for loops.
Well, if you're looking for good REPL resources, I just happened to have done a 3 hour Julia REPL Mastery for a previous JuliaCon.
https://www.youtube.com/watch?v=bHLXEUt5KLc
I should shrink this a more bite-sized version at some point.
Learning just enough R (or Julia or Python) for the purposes of making replicable, easily updated, easily styled, easily adjusted tables and figures for research is something that a freshman in social sciences and humanities can handle. And for many academics that level of knowledge is enough for grad school and eventually even tenure.
That level of knowledge will not necessarily include even the most rudimentary software engineering skills: the code will (most likely) live in a single long file. That file will contain both working chunks of code, old/broken/redundant chunks of code as well as comments and notes both related and unrelated to the task at hand. It will never be executable in a linear fashion (only chunks at a time in a REPL). And some data importing or package/library management will still have to be done through a GUI.
At least for some scientific computing codes, but I did learn a lot of Rust along the way and extended my knowledge into (some) embedded and systems programming. Learning has been fun and the Rust people have been very friendly.
Interesting post, even though I don’t understand “Why Rust” because all the reasons cited are not an issue with Julia (eg memory safety).
This seems to not be about Julia at all. It just a post of a person who only knew Julia, and is now wanting to learn another language (which happens to be Rust)
There's a different collection of reasons which wouldn't have that property (smol binary, suitable for realtime and embedded, etc), but it isn't the ones the author picked.
Precisely, the most natural symbiosis for a Julia + Rust combo seems to fill a hard-realtime, critical safety or embedded niche with offline simulation.
The Py03 and maturin story is something that we haven't brought to Julia yet and I'd love to have some time to work on a tighter integration story there. For example, getting a hot-reload with Revise.jl and a bindings generator that watches file changes and recompiles and the fly so that a Julia REPL gets updated with the new definitions would be so enviable.
I haven't spoken to Tim Holy to see how to string that together but many of the pieces are there.
Now I've done a "From Julia to Rust" workshop at the latest JuliaCon and an accompanying survival guide, so that still ongoing work.
I’ll agree that it’s mostly about its like using Rust compared to Julia. It’s not very dismissive of Julia. The first paragraph lets you know what your in for.
Note that Julia does allow memory unsafety, for example you can mark array accesses with `@inbounds` to remove bound checks, kinda like how you can use `unsafe` in Rust except it looks much less scary.
It also doesn't help that the official example for how to use it safe was actually not safe [1]. Granted, this is just a single example and has been fixed since then, but it doesn't give a nice impression of their mindset when dealing with memory safety.
More in general there doesn't seem to be a strong mindset for correctness either. See [2] for a collection of such issues.
Add new builtins to Bash using a shared library (or .bundle on macOS) written in Rust. Bash also allows running bash other commands from native code. (There's a rust crate for creating bash builtins, but it doesn't currently compile on mac due to some improper and some missing linker flags.)