Skip to content

Rust

Rust Logo

Rust is a programming language with a strong focus on performance, reliability, and productivity. That translates to C++-like speeds, best-in-class tooling, a strong focus on good error messages, and designs that shift bug detection to compile time, essentially eliminating segfaults and whole classes of parallelism bugs.

This is particularly interesting to high-performance computing in that it gives you modern tooling and easy access to an ecosystem of libraries without sacrificing speed and great support for parallelism and "low-level" control over your computation.

Using Rust at NERSC

Installing Rust

NERSC provides a Rust module that can be loaded with:

module load rust

Once loaded, you should be able to use cargo as usual.

The module comes with out-of-the-box support for MPI (making sure the mpi crate will pick up on our MPI modules) and bindgen for language bindings (providing the necessary compiler and linker).

Note that the module does not let you change the Rust version and channel via rustup. You can switch channels (i.e., from stable to nightly) using a rust-toolchain.toml file in your project or explicitly adding +nightly to your cargo calls.

You will need your own installation of Rust if you want fine-grained control of the Rust version you are using (which, due to Rust's strong investment in backward compatibility, is mostly needed if you need to be pinned to a particular nightly version). In which case, you can install Rust using the default instructions and contact us for help getting MPI and bindgen running if needed.

NERSC-Specific Quirks

Cargo Home

Cargo places its cache in the CARGO_HOME folder (~/.cargo), which can quickly fill your HOME folder.

If needed, you can deal with this by deleting the git and registry folders in your CARGO_HOME (or running cargo +nightly -Z gc clean gc for less aggressive deletion). This will cause a re-downloading and re-building of the dependencies of your Rust projects but will not otherwise damage your Rust installation.

For a more scalable solution, you can have those folders be symbolic links to a folder in your SCRATCH (this might break in case of a scratch purge but that would still not break your Rust installation) with the following command:

# Create $SCRATCH/cargo_home folder
mkdir -p "$SCRATCH/cargo_home"

# Move git and registry to subfolders in $SCRATCH/cargo_home
mv "$HOME/.cargo/git" "$SCRATCH/cargo_home/git"
mv "$HOME/.cargo/registry" "$SCRATCH/cargo_home/registry"

# Create symbolic links pointing to the moved subfolders
ln -s "$SCRATCH/cargo_home/git" "$HOME/.cargo/git"
ln -s "$SCRATCH/cargo_home/registry" "$HOME/.cargo/registry"

IDE Support

Getting rust-analyzer (and, by extension, VSCode, Emacs, Vim, etc.) to pick up on the Rust module requires having our cargo and RUSTUP_HOME in your environment.

This can be done by adding the following to your ~/.bashrc (if needed, up-to-date paths can be found by loading the Rust module):

export RUST_HOME=/global/common/software/nersc9/rust/stable
export RUSTUP_HOME=${RUST_HOME}/rustup_home
export CARGO_HOME=${HOME}/.cargo
export PATH=${RUST_HOME}/cargo_home/bin:${PATH}

High-Performance Rust

Once you are familiar with the fundamentals of Rust, we highly recommend that you take a look at The Rust Performance Book for further recommendations on making the best use of the available hardware, including a discussion of build configuration settings (i.e. using the target-cpu=native build flag), and a list of compatible profilers.

CPU Parallelism

Rust does not have support for OpenMP. However, its compiler was written with CPU parallelism in mind (i.e., data races are a compile-time bug in Rust) and it has best-in-class support for multi-core computation.

In particular, we recommend that you take a look at the rayon and crossbeam libraries.

Note that, similarly to using dynamic scheduling in OpenMP, those libraries tend to come with a larger base overhead (compared to base OpenMP) in exchange for being better at balancing the work between cores.

MPI

We recommend you use the mpi crate (and, secondarily, the mpi-sys crate if you are missing some functionalities) to parallelize your code over several nodes with MPI.

We currently support both cray-mpi, the default, and mpich. Our Rust module will set the necessary environment variables and ensure that bindgen works.

Switching MPI Modules

The MPI paths are built into your binary statically, at compile time. This means that loading another MPI module before running your binary will not result in using a different MPI implementation. You will need to rebuild your application's binary (which might require calling cargo clean before rebuilding) to ensure that you are using the expected MPI implementation.

GPU Parallelism

We recommend that you use the cudarc crate to call your own CUDA kernels as well as parts of the CUDA Toolkit from Rust.

For more portable GPU support in Rust, you can use, for example, the ocl crate to load kernels written in OpenCL.

Writing kernels in Rust is still very much an experimental practice. You will find up-to-date information on the Rust GPU page.

However, you might find the Arrayfire bindings, which give you access to array computing on GPUs, useful.

SIMD

The Rust compiler has support for SIMD instructions, both architecture-specific (on stable) and portable (currently restricted to nightly).

While you wait for full portable SIMD support to be merged into the compiler, and to benefit from up-to-date ergonomics compared to std::simd, you might want to use the packed_simd crate.

See also the SIMD Book for further information.

Numerical Computing in Rust

The following is a non-exhaustive list of crates that you are likely to need for various numerical computing tasks.

See the Rust Cookbook and Blessed, as well as lib.rs (which has a better search function than crates.io), for a more generalist list of crates.

Linear Algebra

While Rust has bindings for BLAS and LAPACK, you might want to use the nalgebra crate and the nalgebra_sparse crate for dense and sparse linear algebra, respectively.

For an API closer to Numpy, you might like the ndarray crate (it even has a conversion guide for Numpy users).

Random Number Generation

You will likely want the rand crate (which comes with its dedicated book) for your random number generation needs.

See in particular its dedicated page on parallel random number generation.

Machine Learning

Rust has a number of crates for machine learning and in particular deep learning, for which the ecosystem is now very mature.

Regarding deep learning, you might be interested in the Torch bindings or the lighter candle crate, both of which will let you use a PyTorch-like interface.

Others

Other crates you might want to use include:

Interfacing with Other Languages

The following will be useful if you want to either load an existing numerical library in Rust (i.e., something written in Fortran / C / C++) or make your Rust code available to another language (i.e., writing a Rust high-performance kernel for Python code).

In general, you will want to use the extern keyword to generate bindings to some non-Rust code. However, some language-specific crates can make your life significantly easier:

C

You can use the bindgen crate to talk to some C codes in Rust. See their book for detailed usage instructions.

The reverse operation, generating C bindings to Rust code, is covered by the cbindgen crate.

C++

The cxx crate and (higher level) autocxx crate let you call C++ code from Rust and vice versa.

You might also be interested in the cpp crate, which lets you write some C++ code inline inside your Rust code.

Python

pyo3 lets you call Rust code from Python and vice versa.

If you are working specifically on a Rust-based Python package, then you might want to use the maturin build tool to simplify the process.

Getting Help with Rust

If you have questions or problems using Rust at NERSC, do not hesitate to contact NERSC's online help desk!