I suppose I'll just start with the use-case I've found for this: CPython has a sort of calling convention called "vectorcall", which instead of the normal calling convention of PyTupleObject* args, PyDictObject* kwargs
accepts a PyObject** argv
vector, a length, and an immutable tuple that shows which objects in argv are actually kwargs. In thinking about how to implement this in RustPython (currently, we use a similar struct to the standard CC that holds Vec<PyObjectRef>, IndexMap<String, PyObjectRef>
) I realized that there's no way to make it equivalent to CPython (without writing unsafe code) - there's no type in std to represent a borrowed slice (in the lifetime sense) that still has ownership (in the drop sense) over its data. We'd have to either pass a &[PyObjectRef]
, which would involve having to clone the arguments out of the argument vector (not necessarily cheap - we use atomic refcounts for multithreading), or pass a Vec<PyObjectRef>
, which we're doing already.
So, core::slice::OwnedSlice
:
Guide-level explanation
OwnedSlice
is a slice type that borrows memory but semantically owns its contents.
fn process_strings(v: OwnedSlice<'_, String>) {
for owned_string in v {
process(owned_string);
}
}
let mut strings: Vec<String> = get_strings();
// or maybe also a Vec::split_owned_slice(&mut self, range_start: usize) -> OwnedSlice<'_, T>
// that can just set_len(range_start)
let mut drain = strings.drain(5..);
process_strings(drain.as_owned_slice());
Reference-level explanation
pub struct OwnedSlice<'a, T> {
data: *mut [T],
_marker: PhantomData<(&'a mut T, T)>, // idk how to variance
}
impl<'a, T> OwnedSlice<'a, T> {
/// SAFETY: `data` must not be used after being passed to OwnedSlice, as it will have been dropped
pub unsafe fn from_slice(data: &'a mut [T]);
pub fn as_slice(&self) -> &'a [T];
pub fn as_mut_slice(&mut self) -> &'a mut [T];
}
impl<'a, T> Deref for OwnedSlice<'a, T> {
type Target = [T];
}
impl<'a, T> DerefMut for OwnedSlice<'a, T> {}
impl<'a, T> IntoIterator for OwnedSlice<'a, T> {
type IntoIter = OwnedIntoIter<'a, T>;
type Item = T;
}
unsafe impl<'a, #[may_dangle] T> Drop for OwnedSlice<'a, T> {
fn drop(&mut self) { ptr::drop_in_place(self.data) }
}
Prior Art
std::vec::Drain
: similar, and the main mechanism for passing around owned borrowed slices in std, but only works for things that have already been allocated in specifically a std::vec::Vec
bumpalo::boxed::Box<'a, [T]>
: nearly identical (I believe) to this, but only works for things allocated in a bumpalo::Bump