reinventing two-language problem?
It’s the two language solution
Super interested! Would be curious to see what you’ve been learning in the Rust world. I always thought of Julia as the language you reach for for multi-purpose programming (e.g. from reading a CSV into a DataFrame for analysis to making fun random animations ) whereas Rust you’d want for critical infrastructure or command line tools. Curious if you will have any perspectives on how Julia might compare now in the CLI space due to 1.9’s incoming improvements to caching and precompilation.
Hope to see you at JuliaCon soon!!!
Rust is on the other side a bit way too safe for me:
fn main() {
let a = [1, 2, 4];
// Sum of the squares of all of the elements of the array
let mut sum_sq = 1.0;
for x in a.iter() {
sum_sq *= x;
}
println!("sum_sq = {}", sum_sq)
}
errors with
error[E0277]: cannot multiply-assign `{float}` by `&{integer}`
--> src/main.rs:6:16
|
6 | sum_sq *= x;
| ^^ no implementation for `{float} *= &{integer}`
|
I do understand the motivation for that, but having to do explicit type conversions even to multiply a float by an integer to store the result in a previously defined float starts to become too cumbersome.
that’s a registered trade mark by X + Python
Completely agree that numeric conversion papercuts for people trying STEM stuff in Rust are capable of hemorrhaging the joy of computing out of anyone.
One of my many gripes in Rust land (and boy do I have a few, in particular to the technical computing aspects) is that you have to rethink from loops to iterators to solve your problems.
This isn’t bad per se but does have a learning curve and (potentially) amazing parallelism payoffs.
I would rewrite your code as
fn main() {
let a = [1,2, 4];
let res: i32 = a.iter().map(|e| e*e).sum();
println!("{res}");
}
This idiom is required because for loops with normal indexing are subject to bounds checking by default, which is likely to kill SIMD and other optimizations.
The added benefit is that you can make this entire loop parallel (and data-race free) with the rayon
crate and by just doing .iter() -> .par_iter()
.
…and I find that sort of approach powerful.
and if you’re wondering why this says .map(|e| e*e)
and not .map(|e| e^2)
it’s because ^
in Rust is bitwise xor which is… painful.
Math does not usually look like math in Rust and that’s one of the many pain points of why I don’t think Rust is (yet) a good fit for technical computing.
We may still have to contend with it as “user friendly Fortran with scant package management woes”, but do come to the BoF for more in-depth discussion and spicier takes
That starts to look like numpy
I’ve spent the last 6 months casually learning about Rust. I think the popularity behind Rust is really driven by how it’s revolutionized putting safe memory management in the hands of users. As some here have pointed out, to some degree this is simply making it more difficult to do anything without actively thinking about safety. I assume we’ll see the pendulum swing the other way on some of that because it goes past helping developers be safe to getting in the way of knowledgeable developers that just want to get something done.
I’ve also looked into the traits thing for Rust and there’s nothing especially magical about it. The ability to inherit/propagate traits to new types has a specific mechanism under the hood (the derive attribute). We could implement things like this in Julia but it involves additional compilation steps.
Clearly interactive code is better for scientific computing in many instances and I don’t see how Rust could ever accomplish this without radically changing its entire approach (only compile code that can be proven safe). It’s kind of like asserting that Julia should be used as the primary tool in constructing an operating system. Both could be possible eventually but you’re looking at years of work outside of the current purview.
Rust is wonderful, but sometimes it takes too much workaround to get things down. For example, I can not serialize Box<dyn MyTrait>
, with MyTrait
implemented some types with generic parameters impl<T> MyTrait for MyStruct<T>
. To get to that, I have to use enum
or Box
everywhere, then in each implementation add #[typetag]
. That make the codes very complicated and limited.
What made me to learn Rust is the problem of memory fragmentation, when I run the Julia code in Jupyter again and again, the program became slower and slower. But recently I found out that Rust has the same problem.
But in my personal view, although writing Julia code is more enjoyful than Rust, but Rust is really powerful in some other aspects. It has a bigger community than Julia, it has wonderful toolkits such as rust analyzer, which make refactoring easy, I don’t have to take much care to write high performance code.
What I said above is just my personal narrow view, I just learned Julia for 15 months and Rust for 3 months, so don’t take that seriouly.
I’m afraid the notation was lifted almost directly from Ruby (which some early Rustaceans were close to in the early days:
a = [1,2,4]
b = a.map{|x| x * x}.sum
Ahh, you’re right here. So with Julia, string indexing must be the same internally as Rust’s .chars().nth(idx).unwrap()
. The Rust code is more verbose, less convenient/obvious, but more explicit. In a way this tiny example reflects the broader differences in experiences these languages bring to the developer. I find Rust to have a steeper learning curve than other modern languages, and at least for me it takes a little longer to develop in. I do love Rust. There are some really great things about it, but there are definitely trade-offs.
For scientific computing, you usually want your focus to be on the computing so-to-speak, not on object lifetimes, stack vs heap, and the other nitty-gritties that Rust demands your awareness of. A garbage collector is a wonderful thing if you don’t mind a very small runtime penalty, less fine-grained control over memory management, and slightly bigger binaries.
Rust and Julia are both building on LLVM and if you lurk some forums you can see Rust is pushing up against some issues in LLVMs memory model which I anticipate being incorporated in future llvm releases. So I don’t think it’s an all or none thing here but you’re looking at some aspects of Rust that are pushing up against what it’s toolset can do (and ours).
Stefan Gränitz is mentoring a GSoC for optimizing those specific parts of the LLVM/Rust infra if people are interested in speeding that up
If you’re really interested in memory management and safety in Julia this seems like the perfect time to start learning the internals and create a formal proposal of how Julia could adopt some of these advances into a formal API.
A good example is Bumper.jl. It is a proof of concept but pretty cool package. If I had the know-how knowledge, I would have delved in since I really like to see more packages like this on semi-manual memory management.
That’s probably the sort of conceptualization that we need for a proposal at this point. It doesn’t need to be optimized or even fully safe. Just something given proper native support could be made optimized and safe. That would handle half the work, naming stuff and formalizing a hierarchy of rules.
This code doesn’t do sum of squares.
Heads up, people here might be interested in the ScientificComputing in Rust 2023
See you there!
Rust repl evcxr is nice, but is fay away behind IJulia. And repl is really import for scientific computing.