Repository: dtolnay/cxx (language: rust)

Issue:
Retrieve slice from CxxVector
There is currently no way to get a slice out of a CxxVector. Let's add the a new get_slice() helper so that rust code can use a slice. 

Background, we're doing some C++ -> rust API in-house and there's been a wish for that functionality to have "nicer C++ APIs".

Yes, one can use a slice in the FFI API as a work-around. I.e. use rust::Slice<T> in C++.

Pull request incoming.


Discussion / hints:

Fix patch:
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 1c21770f2..955ad5f50 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -859,12 +859,12 @@ fn expand_cxx_vector(namespace: &Namespace, elem: &Ident) -> TokenStream {
                 }
                 unsafe { __vector_size(v) }
             }
-            unsafe fn __get_unchecked(v: &::cxx::CxxVector<Self>, pos: usize) -> &Self {
+            unsafe fn __get_unchecked(v: &::cxx::CxxVector<Self>, pos: usize) -> *const Self {
                 extern "C" {
                     #[link_name = #link_get_unchecked]
                     fn __get_unchecked(_: &::cxx::CxxVector<#elem>, _: usize) -> *const #elem;
                 }
-                &*__get_unchecked(v, pos)
+                __get_unchecked(v, pos)
             }
             fn __unique_ptr_null() -> *mut ::std::ffi::c_void {
                 extern "C" {
diff --git a/src/cxx_vector.rs b/src/cxx_vector.rs
index 78b8f205c..9d04cb535 100644
--- a/src/cxx_vector.rs
+++ b/src/cxx_vector.rs
@@ -4,6 +4,7 @@ use core::fmt::{self, Display};
 use core::marker::PhantomData;
 use core::mem;
 use core::ptr;
+use core::slice;
 
 /// Binding to C++ `std::vector<T, std::allocator<T>>`.
 ///
@@ -44,7 +45,7 @@ where
     /// out of bounds.
     pub fn get(&self, pos: usize) -> Option<&T> {
         if pos < self.len() {
-            Some(unsafe { T::__get_unchecked(self, pos) })
+            Some(unsafe { self.get_unchecked(pos) })
         } else {
             None
         }
@@ -61,7 +62,24 @@ where
     ///
     /// [operator_at]: https://en.cppreference.com/w/cpp/container/vector/operator_at
     pub unsafe fn get_unchecked(&self, pos: usize) -> &T {
-        T::__get_unchecked(self, pos)
+        &*T::__get_unchecked(self, pos)
+    }
+
+    /// Returns a slice to the underlying contiguous array of elements.
+    pub fn as_slice(&self) -> &[T] {
+        let len = self.len();
+        if len == 0 {
+            // The slice::from_raw_parts in the other branch requires a nonnull
+            // and properly aligned data ptr. C++ standard does not guarantee
+            // that data() on a vector with size 0 would return a nonnull
+            // pointer or sufficiently aligned pointer, so using it would be
+            // undefined behavior. Create our own empty slice in Rust instead
+            // which upholds the invariants.
+            <&[T]>::default()
+        } else {
+            let ptr = unsafe { T::__get_unchecked(self, 0) };
+            unsafe { slice::from_raw_parts(ptr, len) }
+        }
     }
 }
 
@@ -122,7 +140,7 @@ where
 pub unsafe trait VectorElement: Sized {
     const __NAME: &'static dyn Display;
     fn __vector_size(v: &CxxVector<Self>) -> usize;
-    unsafe fn __get_unchecked(v: &CxxVector<Self>, pos: usize) -> &Self;
+    unsafe fn __get_unchecked(v: &CxxVector<Self>, pos: usize) -> *const Self;
     fn __unique_ptr_null() -> *mut c_void;
     unsafe fn __unique_ptr_raw(raw: *mut CxxVector<Self>) -> *mut c_void;
     unsafe fn __unique_ptr_get(repr: *mut c_void) -> *const CxxVector<Self>;
@@ -145,14 +163,14 @@ macro_rules! impl_vector_element {
                 }
                 unsafe { __vector_size(v) }
             }
-            unsafe fn __get_unchecked(v: &CxxVector<$ty>, pos: usize) -> &$ty {
+            unsafe fn __get_unchecked(v: &CxxVector<$ty>, pos: usize) -> *const $ty {
                 extern "C" {
                     attr! {
                         #[link_name = concat!("cxxbridge04$std$vector$", $segment, "$get_unchecked")]
                         fn __get_unchecked(_: &CxxVector<$ty>, _: usize) -> *const $ty;
                     }
                 }
-                &*__get_unchecked(v, pos)
+                __get_unchecked(v, pos)
             }
             fn __unique_ptr_null() -> *mut c_void {
                 extern "C" {


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 2829fef51..9ace1f25f 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -6,7 +6,7 @@
 
 pub mod module;
 
-use cxx::{CxxString, UniquePtr};
+use cxx::{CxxString, CxxVector, UniquePtr};
 use std::fmt::{self, Display};
 
 #[cxx::bridge(namespace = tests)]
@@ -144,6 +144,8 @@ pub mod ffi {
         fn r_take_sliceu8(s: &[u8]);
         fn r_take_rust_string(s: String);
         fn r_take_unique_ptr_string(s: UniquePtr<CxxString>);
+        fn r_take_ref_vector(v: &CxxVector<u8>);
+        fn r_take_ref_empty_vector(v: &CxxVector<u64>);
         fn r_take_rust_vec(v: Vec<u8>);
         fn r_take_rust_vec_string(v: Vec<String>);
         fn r_take_ref_rust_vec(v: &Vec<u8>);
@@ -307,6 +309,16 @@ fn r_take_unique_ptr_string(s: UniquePtr<CxxString>) {
     assert_eq!(s.as_ref().unwrap().to_str().unwrap(), "2020");
 }
 
+fn r_take_ref_vector(v: &CxxVector<u8>) {
+    let slice = v.as_slice();
+    assert_eq!(slice, [20, 2, 0]);
+}
+
+fn r_take_ref_empty_vector(v: &CxxVector<u64>) {
+    assert!(v.as_slice().is_empty());
+    assert!(v.is_empty());
+}
+
 fn r_take_rust_vec(v: Vec<u8>) {
     let _ = v;
 }
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 8dd16e8d9..1f88d8b62 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -399,6 +399,11 @@ extern "C" const char *cxx_run_test() noexcept {
   r_take_rust_string(rust::String("2020"));
   r_take_unique_ptr_string(
       std::unique_ptr<std::string>(new std::string("2020")));
+  r_take_ref_vector(std::vector<uint8_t>{20, 2, 0});
+  std::vector<uint64_t> empty_vector;
+  r_take_ref_empty_vector(empty_vector);
+  empty_vector.reserve(10);
+  r_take_ref_empty_vector(empty_vector);
   r_take_enum(Enum::AVal);
 
   ASSERT(r_try_return_primitive() == 2020);