rust-lang/rust

Can't define unsafe extern "C" fn

Closed this issue · 7 comments

Both

unsafe extern "C" fn foobar() {
    *(0 as *mut u8) = 0;
}

and

extern "C" unsafe fn foobar() {
    *(0 as *mut u8) = 0;
}

give me a syntax error. But it's not the case that an extern "C" function is automatically unsafe. Dropping the unsafe keyword gives me

error: dereference of unsafe pointer requires unsafe function or block

And this code compiles and segfaults:

extern "C" fn foobar() {
    unsafe {
        *(0 as *mut u8) = 0;
    }
}

#[fixed_stack_segment]
fn main() {
    foobar()
}

So I can't declare an extern "C" function (for external linkage, with #[no_mangle]) without also allowing safe Rust code to call that function, breaking memory safety.

I don't quite understand how this breaks memory safety, you explicitly have an unsafe block meaning that it's up to you to guarantee safety (which doesn't happen in this code). It also makes sense to me that you can write an extern "C" function which is indeed safe, you don't necessarily have to force the function to always be unsafe.

It seems like a bug that you can't declare an extern unsafe function, but other than that I'm not sure that there's a problem here.

Right, the bug is that I can't declare a function to be called from C, without allowing safe Rust code to call it too.

The memory unsafety results from the workaround of declaring a safe extern "C" function and using an unsafe block. I can't satisfy the proof obligation of the unsafe block, but there is no other way to write the function.

I agree that you should be able to declare safe extern "C" functions too. But if all extern "C" functions were automatically unsafe then it would at least solve my problem, which is why I included an example to show that isn't the case.

(This came up when I was defining memset in Rust, to satisfy memset calls emitted by rustc/LLVM, in a no-libc environment. I don't want to let safe Rust code call that memset too.)

There seems to be a discrepancy between extern "C" fns defined in Rust and those that bind to native functions. The latter are supposed to be unsafe by default, and since they're the same type, so should the former.

Nominating.

Hmm. It seems to me that for maximum precision, we would set it up like so:

  1. functions declared in an extern block should have the type unsafe extern "C" fn().
  2. Rust functions declared with extern would have the type extern "C" fn().
  3. Rust functions can be declared unsafe as well as extern.
  4. Calling a bare fn is unsafe iff it is unsafe, and does not consider the ABI.

The only downside of this I can see is that the type of C functions is cumbersome. Note that the type system is mostly setup this way (that is, types like unsafe extern "C" fn exist) so it shouldn't be too hard to get it into this state.

1.0, backwards-compat

I just ran into this. I agree with @nikomatsakis's analysis. The type of C functions may be cumbersome, but nobody declaring an FFI function would have to type unsafe extern "C" fn(). The only time anyone should have to type that is when they're implementing an extern "C" function Rust that needs to be unsafe.