Error while initialising object of C++ in Rust passing using FFI

Hi everyone, I am trying to initialize a structure object of Cpp inside the rust by passing struct object from Cpp to Rust by pointer the desired output is coming when I am changing the definition of struct object array2d inside cpp, But I cannot change the definition.
I only have the flexibility to do anything in Rust. I have also tried taking ARRAY as a normal variable inside Rust and then initializing but it doesn’t work as well.

//file: ffi.h
#pragma once
struct dummy{
    public:
        int a;
        int (*array2d)[4];
};
extern "C"
{
    void func1(dummy *x);
}
 
//file: ___.cpp
#include<stdio.h>
#include<iostream>
#include "ffi.h"
using namespace std;
int main(){
    dummy x;
    x.a=1;
    func1(&x);
    cout<<x.a<<endl;
    for(int i=0;i<4;i++){
        for(int j=0;j<4;j++){
            cout<<x.array2d[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}
 
//file: main.rs
pub static ARRAY:[[i32;4];4]= [[1,2,3,4],
                                [1,2,3,4],
                                [1,2,3,4],
                                [1,2,3,4]];
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[repr(C)]
pub struct dummy{
    pub a:i32,
    pub array2d:[[i32;4];4],
}
#[no_mangle]
pub extern "C" fn func1(x:&mut dummy){
    (*x).a=10;
    (*x).array2d=ARRAY;
    println!("inside rust");
	
}

The output is:

inside rust
10
Segmentation fault (core dumped)

If i change
int (*array2d)[4]; → int array2d[4][4]
It is working fine and the output is

inside rust
10
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4

In C++, arrays are not pointers, and pointers are not arrays. The fact that an array is almost always implicitly converted into a pointer to its first element (e.g. when passed to a function) doesn't change the fact that the memory layout of an array-of-arrays is very different from an array-of-pointers (or a pointer-to-arrays, for that matter).

Your C++ struct declares that its member array2d should be a pointer to (an unspecified number of) arrays of 4 integers. I.e., it is a pointer. It takes up as much space as a pointer and no more.

In contrast, the Rust declaration falsely claims that array2d has type array-of-arrays-of-integers. That takes as much space as 16 integers, inline. Therefore, the declaration of your struct in Rust doesn't match its C++ counterpart, hence memory corruption ensues.

You have to match the Rust declaration with the C++ declaration. Again, in Rust, arrays and pointers are not even remotely the same! Arrays are owning values – they store their elements inline, but this is mentioned by the documentation anyway.

Furthermore, the declaration of a struct only affects what code will be generated for the functions accessing it that see that declaration. Types don't exist in machine code, and as such they don't magically travel across languages. If you tell your C++ compiler you have a pointer-typed field, and you tell your Rust compiler you have an array-typed field, then they will try to treat the value as if it had two different types, which is a recipe for disaster.

The fix is, of course, to change either one to match the other. If you can't change the C++ declaration, then why don't you change the Rust declaration to this?

pub struct dummy{
    pub a: i32,
    pub array2d: *const [i32; 4],
}

Then you can write x.array2d = ARRAY.as_ptr(); inside func1().


By the way, you have another instance of undefined behavior in your code. Rust expects a value behind a reference to be fully initialized at all times. However, you don't initialize every field of your object in C++ before passing it to Rust. You should fix that, too (e.g. by setting x.array2d = nullptr; if its value is going to be overwritten anyway).

3 Likes

I have tried this earlier aswell the values are not being mapped correctly.
I have tried it now again as you said and the output is this:

inside rust
10
32 0 0 0
1273538208 32767 1274816548 32641
1220635728 22047 0 0
0 22047 -1819484088 22047

isn't it if I pass a pointer from rust i will have to call rust again to deallocate the memory to avoid memory leakage
for reference:

No.

First of all, leaking memory doesn't corrupt the contents of the memory. It just means that you are using more memory than necessary. Just think about it – it wouldn't make sense if merely keeping around the memory in itself caused it to be overwritten with garbage.

Secondly, in your Rust code, ARRAY is static. That doesn't need to be deallocated at all. (Deallocating it would be undefined behavior, in fact, because it was not allocated dynamically.)

2 Likes

ok,understood this. but the values are not correct when checked in Cpp.Any other approaches?

No, this is the correct approach. If you are still seeing garbage, you are doing something else wrong, too. I couldn't reproduce your problem – the values are printed correctly for me.

1 Like