Search code examples
rustffiunsafe

Why does the second call to my FFI function fail to match the string comparison?


This code shows FFI from Rust to Fortran because it's where I noticed the issue, but I'm pretty sure this is not Fortran specific and maybe even FFI-independent.

I have src/main.rs, a rather minimal thing:

extern crate libc;

extern "C" {
    static mut __wrapper_mod_MOD_a_string: [libc::c_char; 30];
    fn __wrapper_mod_MOD_say_hi();
}

fn main() {
    let s = String::from("hello");
    unsafe { __wrapper_mod_MOD_say_hi() };

    for i in unsafe { __wrapper_mod_MOD_a_string.iter_mut() } {
        *i = ' ' as libc::c_char;
    }

    for (i, c) in unsafe { __wrapper_mod_MOD_a_string }.iter_mut().zip(s.chars()) {
        *i = c as libc::c_char;
    }

    unsafe { __wrapper_mod_MOD_say_hi() };

    for (i, c) in unsafe { __wrapper_mod_MOD_a_string.iter_mut().zip(s.chars()) } {
        *i = c as libc::c_char;
    }

    unsafe { __wrapper_mod_MOD_say_hi() };
}

This calls into src/wrapper.f90:

module wrapper_mod
   implicit none

   private
   public :: say_hi
   character(30) :: a_string

contains

   subroutine say_hi
      if (trim(a_string) == 'hello') then
         write(6,*) 'Howdy there, partner'
      else
         write(6,*) 'Rude of you not to greet me'
      endif
   end subroutine say_hi
end module wrapper_mod

I get the output:

 Rude of you not to greet me
 Rude of you not to greet me
 Howdy there, partner

Why? The only difference in the last two lines is the scoping of the unsafe block. I thought the unsafe action is the access over FFI, but once I have an array, it should be "safe" to iterate it as I please. Clearly I've misunderstood something.

My Cargo.toml has cc = "1.0" in [build-dependencies] and I have the following build.rs:

extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/wrapper.f90")
        .compile("libwrapper.a");
    println!("cargo:rustc-link-lib=static=wrapper");
    println!("cargo:rustc-link-lib=dylib=gfortran");
}

Solution

  • There's nothing particular about the use of unsafe here. The same thing happens with regular curly braces:

    fn main() {
        let mut bytes = [0; 4];
        let new_bytes = b"demo";
    
        for (i, &c) in { bytes }.iter_mut().zip(new_bytes) {
            *i = c;
        }
    
        println!("{:?}", bytes);
        // [0, 0, 0, 0]
    
        for (i, &c) in { bytes.iter_mut().zip(new_bytes) } {
            *i = c;
        }
    
        println!("{:?}", bytes);
        // [100, 101, 109, 111]
    }
    

    Usage of the curly braces forces a move of the variables inside the braces. Since [libc::c_char; 30] and [u8; 4] both implement Copy, an implicit copy is made due to move. You can take a mutable reference and move that through the curly braces:

    for (i, &c) in { &mut bytes }.iter_mut().zip(new_bytes) {
        *i = c;
    }
    

    See also: