- 🧠 Rust's ownership model and FFI support can safely use legacy Fortran code for high-performance scientific computing.
- 📊 Fortran’s numerical performance is still faster than many modern alternatives, especially with optimized libraries like BLAS or LAPACK.
- 🔗
bind(C)in Fortran provides ABI compatibility and avoids linker errors when called from Rust. - ⚠️ Most cross-platform FFI issues come from name mangling and compiler differences, not code-level bugs.
- 🔒 Breaking FFI into modules with safe Rust wrappers keeps unsafe logic separate and makes it simpler to maintain.
Rust and Fortran might seem like an odd pair—one is a 21st-century leader in safe systems programming, and the other is a decades-old, well-known tool in scientific computation. But when fast, accurate work meets safe, modern code, you can do a lot. This article shows how to link Fortran to Rust using Rust FFI. It provides a full guide to techniques, problems, and good practices for developers who want to use both languages for fast, reliable, and maintainable software.
Understanding Rust FFI
Rust’s Foreign Function Interface (FFI) is the main way languages work together in multi-language systems. It lets Rust code call—and be called by—functions that follow the C Application Binary Interface (ABI). While made for C compatibility, this also lets you link Fortran—with the right preparations.
Key Rust FFI Concepts
-
extern "C"Declarations
These tell the Rust compiler the function you’re linking to uses the C calling convention. The"C"keyword makes sure of ABI compatibility and allows linkage during compilation. -
unsafeBlocks
All FFI calls in Rust are marked unsafe because the compiler cannot check memory safety promises. This is a promise: you're responsible for making sure no things go wrong. -
FFI Use Cases
Use Rust FFI when:- You have a Fortran module that works well and has known performance.
- You’re slowly moving your code to Rust.
- You depend on numerical libraries with no similar tools in Rust (e.g., LAPACK).
Basic Example
extern "C" {
fn add_numbers(a: f64, b: f64) -> f64;
}
fn main() {
unsafe {
println!("Sum: {}", add_numbers(2.0, 3.5));
}
}
This example assumes the add_numbers function exists in an external shared/static library, compiled with a Fortran or C compiler that follows the appropriate ABI.
Fortran’s ABI and Binding Expectations
Fortran, depending on compiler and version, changes function names in different ways. This often stops languages from linking. Understanding and controlling this behavior is key when working with Rust FFI.
Common Fortran Name Mangling Patterns
- Function name lowercasing:
ADD_NUMBERSbecomesadd_numbers. - Trailing underscores: gfortran may convert
add_numbersintoadd_numbers_. - Module scoping: Functions inside modules might become
__module_MOD_funcor worse.
The Modern Solution: bind(C)
The bind(C) attribute in newer Fortran compilers (Fortran 2003+) lets you stop name changes and make sure it works with C ABI.
subroutine add_numbers(a, b, result) bind(C, name="add_numbers")
use iso_c_binding
real(c_double), intent(in) :: a, b
real(c_double), intent(out) :: result
end subroutine add_numbers
This makes sure that the subroutine is exported directly as add_numbers, with C-compatible types and how it is called.
Preparing Fortran Code for Rust FFI
To connect them, you need to make sure the Fortran code works in a way Rust understands.
Best Practices
- Use
iso_c_binding: Makes sure all types match up with C (and thus Rust) expectations. For example:c_doublemaps to Rust'sf64c_intmaps to Rust'si32
- Avoid COMMMON blocks: Shared memory segments can make the memory hard to see and unsafe to handle from Rust.
- Prefer subroutines over functions: Rust works better with external C functions when Fortran uses
subroutine, especially when returning complex results. - Use fixed-size arrays carefully: Fortran uses column-major order, but Rust uses row-major.
Sample Fortran Subroutine
subroutine matrix_multiply(a, b, c, n) bind(C, name="matrix_multiply")
use iso_c_binding
integer(c_int), intent(in) :: n
real(c_double), intent(in) :: a(n,n), b(n,n)
real(c_double), intent(out) :: c(n,n)
end subroutine
This is good for linking with Rust. All parameters are passed by reference, and types are predictable.
Declaring Fortran Functions in Rust
To call Fortran code from Rust, you must declare an extern "C" function with types and pointers matching the Fortran signature.
Rust FFI Declaration for matrix_multiply
extern "C" {
fn matrix_multiply(
a: *const f64,
b: *const f64,
c: *mut f64,
n: i32,
);
}
Example Usage
let n = 3;
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; // Column-major layout
let b = a.clone();
let mut c = vec![0.0f64; n * n];
unsafe {
matrix_multiply(a.as_ptr(), b.as_ptr(), c.as_mut_ptr(), n as i32);
}
Tip: You may need to convert to/from column-major layout. Rust’s ndarray crate can help here.
Automating Fortran Builds with build.rs
Rust’s build script system lets you set up special compilation steps. The cc crate lets you call Fortran compilers easily as part of the build process.
Sample build.rs
fn main() {
cc::Build::new()
.file("src/compute.f90")
.compiler("gfortran")
.flag("-O3")
.compile("libcompute.a");
}
This tells Cargo to compile src/compute.f90 into a static library libcompute.a using gfortran at optimization level 3.
Add to Cargo.toml
[build-dependencies]
cc = "1.0"
File Structure
project-root/
├── src/
│ ├── main.rs
│ └── compute.f90
├── build.rs
├── Cargo.toml
Cross-Platform Compatibility
Running your project across macOS, Linux, and Windows shows problems in FFI decisions. Many compilers, linkers, and ABIs behave differently.
Tips for Multi-Platform FFI
-
On macOS, link with
System:println!("cargo:rustc-link-lib=dylib=System"); -
On Linux:
println!("cargo:rustc-link-lib=gfortran"); println!("cargo:rustc-link-lib=m"); -
Use
std::env::var("TARGET")to detect the platform and build differently for each one.
Cross Compilation
If you're building for ARM systems or unusual environments, set up cross-compilation toolchains for both Rust and Fortran. Consider cross or cargo-zigbuild to make this easier.
Troubleshooting: Top Issues in Fortran Rust Linking
When linking Fortran to Rust, most issues aren’t deep bugs—they’re things that don't match up. Below are common errors and how to find out what's wrong.
1. Symbol Not Found
- Issue: Rust expects
add_numbers, but the Fortran compiler exportedadd_numbers_. - Fix: Use
bind(C, name="add_numbers")in Fortran to set the symbol exactly.
2. Segmentation Faults
- Cause: Misaligned or uninitialized pointers. Fortran expects inputs by reference.
- Fix:
- Check lifetime of Rust buffers.
- Make sure matrix dimensions and declared sizes match exactly.
3. Undefined Reference Errors
- Cause: Link order or omitted dependencies.
- Fix: If linking manually, fortran libraries come after Rust binaries. Make sure
cargo:rustc-link-libincludes everything.
Debugging Tools
nmorobjdump -tshows symbols exported by.oor.afiles.- Use
println!in Rust andwrite(*,*)in Fortran for temporary output. - Debug with
gdborlldb, setting breakpoints in both Rust and Fortran code.
Advanced Integration: Rust with LAPACK
The lapack and blas-src crates let you use fast linear algebra libraries, most written in Fortran.
Example: Calling dgesv_ for Solving Linear Systems
extern "C" {
fn dgesv_(
n: *const i32,
nrhs: *const i32,
a: *mut f64,
lda: *const i32,
ipiv: *mut i32,
b: *mut f64,
ldb: *const i32,
info: *mut i32,
);
}
You must link against system LAPACK (OpenBLAS, MKL, etc.):
println!("cargo:rustc-link-lib=lapack");
println!("cargo:rustc-link-lib=blas");
Performance Tip
When solving large matrix problems (n > 1000), calling LAPACK from Rust remains faster than most pure-Rust alternatives.
Testing and Debugging Mixed Codebases
Testing Rust code that depends on Fortran-linked functions can be hard.
Solutions
-
Mock Fortran logic in tests:
#[cfg(test)] fn add_numbers(a: f64, b: f64) -> f64 { a + b } -
Use
#[cfg(test)]to compile either mock or real functions. -
Advanced debugging with
gdborlldb: Stepping through both Fortran and Rust source files helps find tricky bugs.
Best Practices for Maintainability
Modular Design for FFI
Create a bindings or ffi module to separate all unsafe blocks and keep your application logic clean.
pub mod ffi {
extern "C" {
pub fn add_numbers(a: f64, b: f64) -> f64;
}
pub fn safe_add(a: f64, b: f64) -> f64 {
unsafe { add_numbers(a, b) }
}
}
Document Assumptions
- Clearly state ABI conventions.
- Note required compiler versions.
- Track Fortran changes that can affect Rust logic.
When (and If) to Migrate Fortran to Rust
Sometimes, it’s better to move all your code to Rust. But when should you?
Signs It’s Time to Migrate
- Your development team struggles with old tooling.
- Memory bugs slip through despite a lot of testing.
- Your use case needs a lot of low-level work like networking or parallelism.
When to Stick With Linking
- Your Fortran code works very well for numerical routines.
- You rely on tested Fortran libraries like FFTPACK or LAPACK.
- Speed is critical, and rewriting it might make it worse.
C Bridge Strategy
Make a “middle layer” in C that connects Fortran and Rust, especially for complex APIs. This can reduce problems between Rust and Fortran.
Final Thoughts: Use Both Languages Well
Connecting Fortran and Rust isn't just possible—it's useful and often very fast. By understanding FFI, managing ABI compatibility with bind(C), and wrapping unsafe code carefully, you can use Fortran's many math tools and get the good parts of Rust’s safety and tooling.
Use Rust where safety and clarity matter. Use Fortran where mature math matters. And link them like a pro.
As they say, make it work first. Then, make it right. Then, make it fast—and you’ll find Fortran Rust linking gives you all three.
Citations
-
Shende, D., & Malony, A. D. (2006). Performance profiling and tracing for scientific computing. Scientific Programming, 14(3-4), 287–295.
https://dx.doi.org/10.1155/2006/34047 -
Rust Foundation. (2022). Rust Programming Language Annual Survey 2022.
https://foundation.rust-lang.org/blog/2023/02/rust-annual-survey-2022/ -
National Energy Research Scientific Computing Center (NERSC). (2021). Fortran + Modern Tools Report.
https://www.nersc.gov/news-publications/nersc-news/nersc-center-news/2021/fortran-with-modern-tools-report/