On Julia’s future (Developer Experience in the Era of Generative AI)
Hiroshi Shinaoka & Satoshi Terasaki
- Why we moved from C++ to Rust (Developer experience in the era of generative AI)
- Goodbye
libsparseir, hellosparse-ir-rs - Introducing RustToolChain.jl: a small utility for integrating Julia and Rust
- An experimental Rust port of tensor4all tensor network ecosystem with Julia/Python bindings via C-API
We actively use two languages with very different characteristics, Julia and Rust, for scientific computing. Precisely because this is an unusual position, we decided to write down what we have felt.
Assumptions
Dynamic languages such as Julia and Python are convenient to use interactively (REPL, Jupyter Notebook). It is easy to keep results in memory, perform further analysis depending on those results, and visualize them. Among these, Julia stands out by also sharing properties of statically typed languages: it is easier to write type-safe code and achieve high performance.
In summary, Julia's strengths are:
- Ease of use in interactive environments
- Type safety by also sharing properties of statically typed languages
- High performance, enabling pure-Julia implementations from low level to high level (addressing the two-language problem)
- Dependency resolution via an excellent package system
On the other hand, Rust prioritizes memory safety and introduces an ownership system. The ownership system is an approach at the opposite end from garbage-collected memory management as in Julia and Python. In Julia and Python, it is easy to accidentally keep multiple mutable references to the same object, causing subtle bugs where memory contents change without noticing.
In contrast, Rust's ownership system can guarantee memory safety at compile time. Having an excellent package system is common to both Rust and Julia, making it easier to build ecosystems collaboratively (we avoid the dangerous topic of which one is better). The trade-off is increased complexity in writing code, and a steep learning curve for humans.
How we use Julia and Rust
As we wrote in earlier articles, this combination provides a good development experience and makes it easier to grow ecosystems collaboratively. Because both have excellent package systems, it is easy to build Julia libraries with Rust backends.
In principle, Julia can cover the entire stack from low level to high level in a single language, but the cost of optimizing a huge codebase is not small. Even with static analysis tools such as JET.jl, as long as dynamic coding styles are possible, there is a limit to what can be guaranteed statically. Moreover, for high-performance parallel execution, thread startup costs and garbage collection overhead cannot be ignored. Whether one should pay the cost of carefully writing highly optimized large code depends on the size of the codebase.
Meanwhile, a strength of Julia is that a huge codebase can be split into multiple components and published as independent Julia packages. However, Julia's package registration system is issue-based on GitHub, and new packages or new versions are not published immediately. Managing versions across many mutually dependent libraries is extremely painful.
From personal experience, when writing large backend code, as long as support from AI agents is available, writing in Rust feels comfortable. Rust code can look very complex due to ownership annotations, and one might think it is hard for humans to review code generated by AI agents. In practice, however, the compiler validates ownership constraints, so humans can focus on higher-level checks (correctness of algorithms and data structures). Also, multiple packages (crates) can be grouped in a single repository and managed under a shared version (the workspace feature). Publishing crates can also be done immediately from the terminal using cargo publish, effectively enabling a set of crates to be released together.
Historically, combining languages often leads to the "two-language problem." For example, combining C++ and Python is something we would never want to do again ourselves: distributing libraries, managing dependencies, and managing versions become difficult in countless ways (Goodbye libsparseir, hello sparse-ir-rs). Rust and Julia can interoperate easily through C-FFI, and both can leverage excellent package systems, so these problems are less severe than before.
Even so, distributing libraries that combine Julia and Rust still requires specialized knowledge. The RustToolChain.jl introduced above is a tool to address this. With the help of generative AI, this area will further improve.
The boundary between Julia and Rust shifts: the more generative AI advances, the more territory becomes favorable to Rust. Once an era arrives where natural language can handle everything end-to-end, from data analysis to visualization (it is near), humans do not care what tools the AI uses behind the scenes. At that point, the role of dynamic programming languages as a bridge between humans and machines will be replaced by natural language.