I have the following problem in my program: There is a structure that I share between Rust and C code. It is created in Rust code and goes out of context in Rust code, so for a pointer in this struct I want to take advantage of the automatic memory management of Box. It seems to have the correct memory representation, so I just put a Box into the #[repr(C)] struct.
I could of course convert the Box to a raw pointer to store it in the struct and then implement Drop, but this seems unnecessary as this works perfectly fine like this already. What would you recommend?
When a type does not have an #[repr(...)] annotation, the memory layout of that type is 100% unspecified, and could be anything. Of course, if it happens to be what you expected in this particular instance, it will work, but then you are relying on internal compiler details.
The case of box is slightly interesting, because the standard library does make a promise in the documentation, so in this case it is ok. However you should be aware that the only reason they can make this promise is that Box has been implemented by the people who wrote the compiler, so they can make promises that rely on internal compiler details, and this is only because they also control the compiler, and control thus when and how these internal compiler details change.
As for “but this seems unnecessary as this works perfectly fine like this already”, that is a very dangerous argument when you are dealing with undefined behaviour. Let me quote myself
In this case it wasn't just luck, but the there are other cases where it will just be luck, and in those cases you could have made the same argument, and therefore it is a bad argument.
I don't know why they haven't just put #[repr(transparent)] on box.
@alice actually, for Box and Box only, even if it is not #[repr(transparent)], Rust now guarantees it having the layout of a ptr::NonNull<T> (at least when T : Sized)
EDIT: Misread the comment.
This is due to the improper_ctypes lint, which is "known to be overly conservative", which has led to it being disabled for extern "C" fndefinitions. In this case I guess that "the Box has the layout of ptr::NonNull" rule hasn't been integrated into the lint, hence the error.
Assuming:
UserData : Sized
which you can ensure by adding the requirement to the struct definition:
UserData is FFI-compatible OR the C code never accesses UserData fields
this second case seems the most likely for an extern { function declaration: we can imagine C code having a struct { uint32_t something; void * ignored; } for implementors to use the void * as they please.
then your extern "C" function declaration is fine, and you can slap a #[allow(improper_ctypes)] on it with a comment ont top linking to this very thread or summarizing it.
Note, however, that by using Box<_> directly rather than Option<Box<_>>, you really need to ensure that C never causes the pointer to become NULL, as that would be instant UB the moment such ill-formed value gets to exist in the Rust world.
For instance, a do_something that "initializes" the something field of MyStruct by doing:
Also note that you are using u32 for your something field; given the do_something declaration, one can imagine that that may be the field C interacts with. And C has a history of using int as its integer type. If that's the case in your project, you should be using c_int instead of u32 (but of course if they are using uint32_t like my example does, then it is fine).
I did comment on this in my second paragraph; I just also felt the need to comment on the “but this seems unnecessary as this works perfectly fine like this already” thing.