Description
Arrays are passed to C as a raw pointers, which means that foo
does not move it, so this example is confusing at best.
#[repr(C)]
struct I32(i32);
extern "C" {
fn foo(x: [I32; 2]);
}
fn main() {
let x = [I32(0), I32(1)];
unsafe { foo(x) };
println!("x = {}", x[0].0);
}
Errors:
Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `x`
--> src/main.rs:11:24
|
10 | unsafe { foo(x) };
| - value moved here
11 | println!("x = {}", x[0].0);
| ^^^^^^ value borrowed here after move
|
= note: move occurs because `x` has type `[I32; 2]`, which does not implement the `Copy` trait
error: aborting due to previous error
If one does not try to use the moved value, this will silently compile, but x
will be deallocated as soon as the function returns, yet the C code could still try to read (or even write - the code above doesn't make it clear what C can actually do with the pointer...) to it.
It would be better if we would require code to be more explicit about this, e.g., by writing:
extern "C" {
fn foo(x: *const [I32; 2]);
// or:
fn foo(x: *mut [I32; 2]);
}
instead. This makes it clear that foo
doesn't own the array, how many elements are expected behind the pointer, and whether the foreign function only reads or also might write to it.
We could avoid breaking changes due to updating C FFI code by allowing people to still call foo(x)
but treating it a as a unique or shared borrow depending on the mutability of the FFI declaration, and then applying a coercion to the raw pointer, while simultaneously emitting a warning to users that they should be more explicit and write foo(&x as *const _)
instead.