What is the correct way to convert Vec to FFI without redistribution?

I need to pass Vec elements through FFI. While experimenting, I came across several interesting points. I started by providing FFI to all 3: ptr , len and capacity , so that I can restore Vec to destroy it later:

 let ptr = vec.as_mut_ptr(); let len = vec.len(); let cap = vec.capacity(); mem::forget(vec); extern_fn(ptr, len, cap); // ... pub unsafe extern "C" fn free(ptr: *mut u8, len: usize, cap: usize) { let _ = Vec::from_raw_parts(ptr, len, cap); } 

I wanted to get rid of capacity , since it is useless for my interface; it’s just that I can recover my vector to free up memory.

Vec::shrink_to_fit() is tempting because it seems to eliminate the need to deal with an ability. Unfortunately, the documentation on it does not guarantee that it will do len == capacity , so I guess that from_raw_parts() will most likely trigger Undefined Behavior.

into_boxed_slice() seems to have a guarantee that it is going to make len == capacity from docs , so I used this in the following. Please correct me if I am wrong . The problem is that it does not seem to guarantee redistribution. Here is a simple program:

 fn main() { let mut v = Vec::with_capacity(1000); v.push(100u8); v.push(110); let ptr_1 = v.as_mut_ptr(); let mut boxed_slice = v.into_boxed_slice(); let ptr_2 = boxed_slice.as_mut_ptr(); let ptr_3 = Box::into_raw(boxed_slice); println!("{:?}. {:?}. {:?}", ptr_1, ptr_2, ptr_3); } 

On the playground, he prints:

 rustc 1.14.0 (e8a012324 2016-12-16) 0x7fdc9841b000. 0x7fdc98414018. 0x7fdc98414018 

This is not good if he should find a new memory instead of getting rid of extra capacity without causing a copy.

Is there any other way to pass my vector through FFI (before C) and not skip the capacitance? It seems that into_boxed_slice() is what I need, but why is it related to redistributing and copying data?

+6
source share
1 answer

The reason is relatively simple.

Modern memory allocators will allocate allocations in "dimensional" plates, where each panel is responsible for a given range of sizes. For instance:

  • 8-byte plate: all from 1 to 8 bytes
  • 16 bytes: everything from 9 to 16 bytes.
  • 24-byte plate: everything from 17 to 24 bytes
  • ...

When you allocate memory, you ask for the specified size, the allocator finds the desired panel, receives a piece from it and returns your pointer.

When you free up memory ... how do you expect the dispenser to find the right panel? There are 2 solutions:

  • the dispenser has a way to search for a plate that contains your memory range, one way or another, which includes either a linear search through the plates, or some kind of global search table or ...
  • you tell the distributor what size the selected block is

It’s obvious that the C ( free , realloc ) interface is more of a subparameter, so Rust wants to use a more efficient interface instead, the one where the burden is on the caller.


So, you have two options:

  • Transfer bandwidth
  • Make sure the length and capacity are equal.

As you understand, (2) a new distribution may be required, which is completely undesirable. (1) can be implemented either by transferring the entire bandwidth, or by hiding it at some point, and then retrieving it when you need it.

What is it. You must appreciate your tradeoffs.

+8
source

Source: https://habr.com/ru/post/1014185/


All Articles