Assign an array * mut c_void

I am writing bindings for a library where I have a function with a parameter of type void* aka *mut c_void in Rust. I need to assign an array to this parameter, how can I do this in Rust?

I tried casting, transmute , it does not work ( transmute says that c_void and [u8] have different sizes). If that matters, I get a slice from the vector.

UPDATE Perhaps it would be right to somehow use vec.as_mut_ptr () instead?

PLAYPEN : http://is.gd/KjgduZ

+4
source share
1 answer

The API you described looks very suspicious. Remember that in fact there are no โ€œarraysโ€ in C-arrays - this is just another name for pointers to the beginning of several values โ€‹โ€‹of the same type, which are constantly stored in memory. Therefore, it is impossible to simply โ€œassignโ€ an array to C. There are two concepts that can be understood as โ€œassigningโ€ to an array: first, by assigning a pointer to the beginning of the array somewhere:

 const char *s1 = "hello"; const char *s2 = "world"; const char *s = s1; // make `s` contain a pointer to "hello" s = s2; // make `s` contain a pointer to "world" 

Secondly, it is copying certain pieces of data from one pointer to another, which is usually done using memcpy() or something like this:

 const char *s1 = "hello"; char s2[5]; memcpy(s2, s1, 5); // copy 5 bytes from the memory pointed at by `s1` to the memory pointed at by `s2` 

Now you can see what I mean when I say that your API is suspicious. Your callback function is assigned void * , but there is no indication of the use of the array copy method.

If this is the first one, that is, copy the pointer to the beginning of the array, then the void * type is extremely useless. It does not say how this pointer should be displayed. It looks like you are trying to do just that; however, this will not work, as you probably think. Here is a compilation version of your code (note that it is incorrect and most likely your program crashes, see below):

 #![feature(libc)] extern crate libc; use libc::c_void; pub extern fn demo(data: *mut *mut c_void) { let mut vec = vec!(1, 2, 3); unsafe { *data = vec.as_mut_ptr() as *mut c_void; } } 

(Note that you can call as_mut_ptr() on the mut variable containing the vector directly because of autoderef)

The parameter type is now not just *mut c_void , but *mut *mut c_void , that is, it is a pointer to *mut c_void . Thus, the program that calls this function can pass a pointer to a local variable of type void * this function and get a pointer to the actual array, something like

 void *data; some_struct.callback_fn(&data); // pointer to `demo` is stored in `some_struct` // data is now whatever your `demo` function has assigned 

Note that you simply cannot intelligently make a demo only *mut c_void , because the only thing you can do with it is reassign the parameter itself, but reassigning the parameter reassigns only that parameter value, i.e. the local variable that this parameter represents . This cannot be observed outside the function. In other words, the following code (which is also a variant of the one you provided):

 pub extern fn demo(mut data: *mut c_void) { let mut vec = vec!(1, 2, 3); data = vec.as_mut_ptr() as *mut c_void; } 

Does nothing, and Rust is pleased to note this:

 <anon>:6:20: 6:28 warning: variable `data` is assigned to, but never used, #[warn(unused_variables)] on by default <anon>:6 pub extern fn demo(mut data: *mut c_void) { ^~~~~~~~ <anon>:8:5: 8:9 warning: value assigned to `data` is never read, #[warn(unused_assignments)] on by default <anon>:8 data = vec.as_mut_ptr() as *mut c_void; ^~~~ 

The reason I said that the code with *mut *mut c_void wrong is because it actually violates memory security. If you create a Vec instance and store it in a local variable when this variable goes out of scope, the vector itself will be destroyed and the memory that it will wrap will be freed. Therefore, each pointer obtained from it using as_ptr() or as_mut_ptr() will become invalid.

There are several ways to work with this, the simplest is to simply forget() vector:

 use std::mem; let mut vec = vec![1, 2, 3]; *data = vec.as_mut_ptr() as *mut c_void; mem::forget(vec); 

Thus, the vector is "forgotten" - its destructor will not be called. Thus, a memory leak is introduced into your program. Each time demo() is called, a little more memory will be allocated, but it will not be freed, so in the end, your program will use all available memory and possibly crash after that. However, in some contexts these are reasonable things, especially in low-level code. For example, your API may indicate that it will call this function only once.

Another problem with this approach is the logical consequence of the above. Your API may indicate who should free the memory of the pointer provided to it. For example, this may require transferring memory allocated using malloc() , and then it will free it using free() . Or it may indicate that you must define another function that will be called when all allocated memory has to be freed. Somewhat inconvenient to implement in Rust; I will not talk in detail about how to do this, if it really is not your business. In any case, your API should clearly indicate the owner of the memory, and you should take it into account, because Rust describes ownership more clearly.

Another possibility is that your API needs to copy some data into the memory indicated by the void * pointer. In other words, its implementation contains code such as this:

 char buffer[256]; some_struct.callback_fn(buffer); 

and expects that after callback_fn call to buffer will be filled with data.

If so, the API should naturally indicate the maximum number of bytes in the buffer that your program can use, and your demo function might look like this:

 use std::ptr; use libc::c_void; pub extern fn demo(data: *mut c_void) { let vec: Vec<u8> = vec!(1, 2, 3); unsafe { ptr::copy_nonoverlapping(vec.as_ptr(), data as *mut u8, vec.len()); } } 

(alternatively, you can convert data to &mut [u8] using std::slice::from_raw_parts_mut() and use clone_from_slice() or bytes::copy_memory() , but both of them are unstable, so they can "t on rust resistant)

In this case, you should be especially careful not to overflow the buffer provided by the calling program. Its maximum size must be specified in the API.

Another problem is that copying the array is just for byte arrays ( char * on the C side, &[u8]/&mut [u8] on the Rust side). When you start using larger types, such as i32 , you will get the possibility of portability problems. For example, in C int does not have a specific size, so you just cannot blindly convert &[i32] to &[u8] with four times the original size and copy bytes from it to *mut u8 . These problems should be taken very carefully.

+5
source

All Articles