1

I am trying to call a Fortran function from Rust but I am getting this error:

/src/timer.f:4: undefined reference to `_gfortran_cpu_time_4'

I searched through the internet but can't find any solution to this. The Fortran code is:

subroutine timer(ttime)
  double precision ttime
  temp = sngl(ttime)
  call cpu_time(temp)
  ttime = dble(temp) 

  return
end

And the Rust binding is:

use libc::{c_double};

extern "C" {
    pub fn timer_(d: *mut c_double);
}

I don't know what I am doing wrong.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
noshusan
  • 303
  • 2
  • 13

1 Answers1

2

As the commenters said, you need to link to libgfortran.

Specifically, in the Rust world, you should use (or create) a *-sys package that details the appropriate linking steps and exposes the base API. Then build higher-level abstractions on top of that.


However, I didn't seem to need to do any of that:

timer.f90

subroutine timer(ttime)
  double precision ttime
  temp = sngl(ttime)
  call cpu_time(temp)
  ttime = dble(temp) 

  return
end

Cargo.toml

[package]
name = "woah"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

build = "build.rs"

[dependencies]
libc = "*"

build.rs

fn main() {
    println!("cargo:rustc-link-lib=dylib=timer");
    println!("cargo:rustc-link-search=native=/tmp/woah");
}

src/main.rs

extern crate libc;

use libc::{c_double};

extern "C" {
    pub fn timer_(d: *mut c_double);
}

fn main() {
    let mut value = 0.0;
    unsafe { timer_(&mut value); }
    println!("The value was: {}", value);
}

And it's put together via

$ gfortran-4.2 -shared -o libtimer.dylib timer.f90
$ cargo run
The value was: 0.0037589999847114086

Which seems to indicate that this shared library either doesn't need libgfortran or it's being automatically included.

If you create a static library instead (and link to it appropriately via cargo:rustc-link-lib=dylib=timer):

$ gfortran-4.2 -c -o timer.o timer.f90
$ ar cr libtimer.a *.o
$ cargo run
note: Undefined symbols for architecture x86_64:
  "__gfortran_cpu_time_4", referenced from:
      _timer_ in libtimer.a(timer.o)

In this case, adding gfortran allows the code to compile:

println!("cargo:rustc-link-lib=dylib=gfortran");

Disclaimer: I've never compiled Fortran before, so it's very likely I've done something silly.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    `*` dependencies should generally be avoided, something like `libc = "0.2"` is better. – huon May 16 '16 at 12:57
  • @huon I agree with that for a *library*, but in a *binary* it doesn't matter; the `Cargo.lock` will prevent any accidental issues. For a binary, it's better to represent the intent of the programmer in `Cargo.toml` (which in this case is "some libc crate"). – Shepmaster May 16 '16 at 13:00
  • 1
    Yes, if you create a shared library in gfortran, the linker should load the dependencies when used I think. http://stackoverflow.com/questions/7508131/linking-dependencies-of-a-shared-library – Vladimir F Героям слава May 16 '16 at 13:45
  • @Shepmaster, the * dependencies do matter with binaries: it makes running cargo update in future more controlled, as a binary is still tied to the API of its dependencies, e.g. if libc has a reorganisation that moves types/functions around (for this specific code, it probably doesn't matter, but it will presumably be used as part of larger projects). You are right that avoiding `*` is more important for publishing a library, but it shouldn't just be ignored for binaries. – huon May 16 '16 at 22:29
  • @huon but if the build fails after `cargo update`, you just `git reset` the changes to `Cargo.lock` and go about your day, update *just* the package you wanted to (`cargo update --package FOO`), or fix your calling code for the new API. There's not really a difference between that and putting the version in and remembering every few weeks / months to edit `Cargo.toml` to check to see if there's an update. It really comes down to preferred workflow for binaries. – Shepmaster May 17 '16 at 00:02