Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ impl<T> ThinVec<T> {

unsafe {
if !empty_header_is_aligned && self.header().cap() == 0 {
NonNull::dangling().as_ptr()
ptr::dangling_mut()
} else {
// This could technically result in overflow, but padding
// would have to be absurdly large for this to occur.
Expand All @@ -712,6 +712,79 @@ impl<T> ThinVec<T> {
}
}

/// Decomposes a `ThinVec<T>` into its raw components: `(pointer, length, capacity)`.
///
/// Returns the raw pointer to the underlying data, the length of
/// the vector (in elements), and it's capacity (also in elements).
///
/// After calling this function, the caller is responsible for the
/// memory previously managed by the `ThinVec`. one does this by converting the raw pointer and length back
/// into a `ThinVec` with the [`from_parts`] function, since the given pointer is offsetted from the actual allocation pointer.
///
/// [`from_parts`]: ThinVec::from_parts
#[must_use = "losing the pointer will leak memory"]
pub fn into_parts(self) -> (NonNull<T>, usize, usize) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the point of returning pointer / len / capacity separately.

A from_raw / into_raw would be reasonable I guess, but that doesn't work for ZSTs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well capacity is indeed optionnal in theory and be retrieved in the allocation, but length is required for ZSTs, we could have a non-zst variant that just gives a pointer. But without the gecko-ffi feature, how do we properly check if it is a singleton or not ? we might get back a pointer that does'nt have a header, and without the capacity we can't know without doubts if there is one or not, heck even with the cap we can't, but since it has no capacity it did'nt allocate so we can just return a newly created one, and length does'nt helps as it could have allocated then cleared. So we do need the trio for reconstruction.
But at the end of the day, it's the same as Vec, from_parts do have stronger expectations than the Vec counterpart but the idea is the same: you get back a pointer, a length and a capacity, and you now own the underlying buffer and are responsible for it's memory management.
As for a use case, I guess the same ones as Vec::into_parts, you can create a ThinVec, operate on it, then need a way to own the allocation because there is an API that you use that needs it for XYZ reasons, might be for FFI where it expect the pointer of the buffer, but you can't keep the ThinVec alive as now it might alias, or keeping it alive for the whole usage might be difficult.

let data_ptr = unsafe { NonNull::new_unchecked(self.data_raw()) };
let len = self.len();
let cap = self.capacity();
mem::forget(self);
(data_ptr, len, cap)
}

/// Creates a `ThinVec<T>` directly from a pointer, a length, and a capacity.
///
/// # Safety
///
/// This is highly unsafe, due to the number of invariants that aren't
/// checked:
///
/// * The raw pointer must have been previously returned by a call to
/// [`ThinVec<U>::into_parts`], it is undefined behavior if not.
/// * `U` must have the same layout as `T`. This is trivially true if `U` is `T`.
/// * Note that if `U` is not `T` but has the same size
/// and alignment, this is basically like transmuting different types. See [`mem::transmute`] for more information
/// on what restrictions apply in this case.
/// * `length` needs to be less than or equal to `capacity`.
/// * The first `length` values must be properly initialized values of type `T`.
/// * `capacity` needs to be the exact same capacity that the pointer was acquired with.
///
/// The ownership of `ptr` is effectively transferred to the
/// `ThinVec<T>` which may then deallocate, reallocate or change the
/// contents of memory pointed to by the pointer at will. Ensure
/// that nothing else uses the pointer after calling this
/// function.
///
/// [`ThinVec<U>::into_parts`]: ThinVec::into_parts
/// [`mem::transmute`]: core::mem::transmute
pub unsafe fn from_parts(ptr: NonNull<T>, len: usize, capacity: usize) -> Self {
// `padding` contains ~static assertions against types that are
// incompatible with the current feature flags. We also call it to
// invoke these assertions when creating a `ThinVec`, even if we don't need the result.
let padding = padding::<T>();

if Self::is_zst() {
return unsafe {
ThinVec {
ptr: len_to_ptr_unchecked(len + 1),
boo: PhantomData,
}
};
}

unsafe {
if capacity == 0 {
Self::new()
} else {
let header_size = mem::size_of::<Header>();
let ptr = ptr.byte_sub(header_size + padding);
ThinVec {
ptr: ptr.cast(),
boo: PhantomData,
}
}
}
}

/// # Safety
///
/// This is unsafe when the header is EMPTY_HEADER or when T is a ZST.
Expand Down
Loading