Repository: dtolnay/cxx (rust)

Issue:
Support opaque Rust types in a rust::Slice
Currently only primitives and shared types are supported in a slice in the FFI.

The following is currently rejected, but it should be possible to support:

```rust
#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type MyType;
    }

    unsafe extern "C++" {
        fn f(slice: &[MyType]);
    }
}
```

The slice iterators and indexing operations will need to obtain a `stride` at runtime from Rust, exactly like we already do for rust::Vec containing opaque Rust types.

Pull request description:
Support opaque Rust types in a rust::Slice
Fixes #532.

Patch:
diff --git a/gen/src/builtin.rs b/gen/src/builtin.rs
index 1de36e17..b79fc29a 100644
--- a/gen/src/builtin.rs
+++ b/gen/src/builtin.rs
@@ -136,6 +136,7 @@ pub(super) fn write(out: &mut OutFile) {
     ifndef::write(out, builtin.rust_isize, "CXXBRIDGE1_RUST_ISIZE");
     ifndef::write(out, builtin.opaque, "CXXBRIDGE1_RUST_OPAQUE");
     ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_RELOCATABLE");
+    ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_DETAIL_SIZEOF");
 
     out.begin_block(Block::Namespace("detail"));
 
diff --git a/gen/src/write.rs b/gen/src/write.rs
index f7587ce4..541c5696 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -1253,6 +1253,7 @@ impl<'a> ToMangled for UniquePtr<'a> {
 }
 
 fn write_generic_instantiations(out: &mut OutFile) {
+
     if out.header {
         return;
     }
@@ -1311,6 +1312,14 @@ fn write_generic_instantiations(out: &mut OutFile) {
                     write_cxx_vector(out, inner);
                 }
             }
+        } else if let Type::Ident(name) = ty {
+            if Atom::from(&name.rust).is_none()
+                && (!out.types.aliases.contains_key(&name.rust)
+                    || out.types.explicit_impls.contains(ty))
+            {
+                out.next_section();
+                write_rust_sizeof_extern(out, name);
+            }
         }
     }
     out.end_block(Block::ExternC);
@@ -1336,6 +1345,13 @@ fn write_generic_instantiations(out: &mut OutFile) {
                     write_rust_vec_impl(out, inner);
                 }
             }
+        } else if let Type::Ident(name) = ty {
+            if Atom::from(&name.rust).is_none()
+                && (!out.types.aliases.contains_key(&name.rust)
+                    || out.types.explicit_impls.contains(ty))
+            {
+                write_rust_sizeof_impl(out, name);
+            }
         }
     }
     out.end_block(Block::InlineNamespace("cxxbridge1"));
@@ -1366,6 +1382,36 @@ fn write_rust_box_extern(out: &mut OutFile, ident: &Pair) {
     writeln!(out, "#endif // CXXBRIDGE1_RUST_BOX_{}", instance);
 }
 
+fn write_rust_sizeof_extern(out: &mut OutFile, element: &RustName) {
+    let instance = element.to_mangled(out.types);
+
+    writeln!(out, "#ifndef CXX_BRIDGE_RUST_SIZEOF_{}", instance);
+    writeln!(out, "#define CXX_BRIDGE_RUST_SIZEOF_{}", instance);
+    writeln!(out, "::std::size_t cxxbridge1$rust_sizeof${}() noexcept;", instance);
+    writeln!(out, "#endif // CXX_BRIDGE_RUST_SIZEOF_{}", instance);
+
+}
+
+fn write_rust_sizeof_impl(out: &mut OutFile, element: &RustName) {
+    let instance = element.to_mangled(out.types);
+    let typename = element.to_typename(out.types);
+
+    writeln!(out, "#ifndef CXX_BRIDGE_RUST_SIZEOF_IMPL_{}", instance);
+    writeln!(out, "#define CXX_BRIDGE_RUST_SIZEOF_IMPL_{}", instance);
+    out.begin_block(Block::Namespace("detail"));
+    writeln!(out, "template<>");
+    writeln!(out, "::std::size_t rust_sizeof<{}>() {{", typename);
+    writeln!( out, "  return cxxbridge1$rust_sizeof${}();", instance);
+    writeln!(out, " }}");
+    writeln!(out, "template<>");
+    writeln!(out, "::std::size_t rust_sizeof<const {}>() {{", typename);
+    writeln!( out, "  return cxxbridge1$rust_sizeof${}();", instance);
+    writeln!(out, " }}");
+    out.end_block(Block::Namespace("detail"));
+    writeln!(out, "#endif // CXX_BRIDGE_RUST_SIZEOF_IMPL_{}", instance);
+
+}
+
 fn write_rust_vec_extern(out: &mut OutFile, element: &RustName) {
     let inner = element.to_typename(out.types);
     let instance = element.to_mangled(out.types);
@@ -1409,11 +1455,6 @@ fn write_rust_vec_extern(out: &mut OutFile, element: &RustName) {
         "void cxxbridge1$rust_vec${}$set_len(::rust::Vec<{}> *ptr, ::std::size_t len) noexcept;",
         instance, inner,
     );
-    writeln!(
-        out,
-        "::std::size_t cxxbridge1$rust_vec${}$stride() noexcept;",
-        instance,
-    );
     writeln!(out, "#endif // CXXBRIDGE1_RUST_VEC_{}", instance);
 }
 
@@ -1513,11 +1554,6 @@ fn write_rust_vec_impl(out: &mut OutFile, element: &RustName) {
         instance,
     );
     writeln!(out, "}}");
-
-    writeln!(out, "template <>");
-    writeln!(out, "::std::size_t Vec<{}>::stride() noexcept {{", inner);
-    writeln!(out, "  return cxxbridge1$rust_vec${}$stride();", instance);
-    writeln!(out, "}}");
 }
 
 fn write_unique_ptr(out: &mut OutFile, ident: &RustName) {
diff --git a/include/cxx.h b/include/cxx.h
index 79b9f540..2ac4361c 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -30,6 +30,14 @@ template <typename T>
 class impl;
 }
 
+#ifndef CXXBRIDGE1_DETAIL_SIZEOF
+#define CXXBRIDGE1_DETAIL_SIZEOF
+namespace detail {
+template <typename T>
+std::size_t rust_sizeof();
+} // namespace detail
+#endif // CXXBRIDGE1_DETAIL_SIZEOF
+
 #ifndef CXXBRIDGE1_RUST_STRING
 #define CXXBRIDGE1_RUST_STRING
 // https://cxx.rs/binding/string.html
@@ -156,6 +164,8 @@ public:
   T *data() const noexcept;
   std::size_t size() const noexcept;
   std::size_t length() const noexcept;
+  T &operator[](std::size_t n) noexcept;
+  T &at(std::size_t n);
 
   // Important in order for System V ABI to pass in registers.
   Slice(const Slice<T> &) noexcept = default;
@@ -167,6 +177,7 @@ public:
 
 private:
   friend impl<Slice>;
+  static std::size_t stride() noexcept;
   // Not necessarily ABI compatible with &[T]. Codegen will translate to
   // cxx::rust_slice::RustSlice which matches this layout.
   T *ptr;
@@ -192,6 +203,7 @@ public:
 private:
   friend class Slice;
   T *pos;
+  std::size_t stride;
 };
 #endif // CXXBRIDGE1_RUST_SLICE
 
@@ -524,16 +536,37 @@ T *Slice<T>::iterator::operator->() const noexcept {
   return this->pos;
 }
 
+template <typename T>
+T &Slice<T>::operator[](std::size_t n) noexcept {
+  auto data = const_cast<char*>(reinterpret_cast<const char *>(this->data()));
+  return *reinterpret_cast<T *>(data + n * this->stride());
+}
+
+template <typename T>
+T &Slice<T>::at(std::size_t n) {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Slice index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+size_t Slice<T>::stride() noexcept {
+  return detail::rust_sizeof<T>();
+}
+
 template <typename T>
 typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept {
-  ++this->pos;
+  auto data = const_cast<char*>(reinterpret_cast<const char *>(this->pos));
+  this->pos = reinterpret_cast<T *>(data + this->stride);
   return *this;
 }
 
 template <typename T>
 typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept {
   auto ret = iterator(*this);
-  ++this->pos;
+  auto data = const_cast<char*>(reinterpret_cast<const char *>(this->pos));
+  this->pos = reinterpret_cast<T *>(data + this->stride);
   return ret;
 }
 
@@ -551,6 +584,7 @@ template <typename T>
 typename Slice<T>::iterator Slice<T>::begin() const noexcept {
   iterator it;
   it.pos = this->ptr;
+  it.stride = this->stride();
   return it;
 }
 
@@ -558,6 +592,7 @@ template <typename T>
 typename Slice<T>::iterator Slice<T>::end() const noexcept {
   iterator it = this->begin();
   it.pos += this->len;
+  it.stride = this->stride();
   return it;
 }
 #endif // CXXBRIDGE1_RUST_SLICE
@@ -752,6 +787,11 @@ const T &Vec<T>::at(std::size_t n) const {
   return (*this)[n];
 }
 
+template <typename T>
+size_t Vec<T>::stride() noexcept {
+  return detail::rust_sizeof<T>();
+}
+
 template <typename T>
 const T &Vec<T>::front() const noexcept {
   assert(!this->empty());
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 15562ac7..84e055a7 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -109,6 +109,12 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
                     expanded.extend(expand_cxx_vector(ident, explicit_impl, types));
                 }
             }
+        } else if let Type::Ident(ident) = ty {
+            let explicit_impl = types.explicit_impls.get(ty);
+            if Atom::from(&ident.rust).is_none()
+                    && (explicit_impl.is_some() || !types.aliases.contains_key(&ident.rust)) {
+                hidden.extend(expand_rust_sizeof(ident, types));
+            }
         }
     }
 
@@ -982,6 +988,20 @@ fn expand_rust_box(ident: &RustName, types: &Types) -> TokenStream {
     }
 }
 
+fn expand_rust_sizeof(elem: &RustName, types: &Types) -> TokenStream {
+    let link_sizeof = format!("cxxbridge1$rust_sizeof${}", elem.to_symbol(types));
+    let local_sizeof = format_ident!("{}__sizeof", elem.rust);
+
+    let span = elem.span();
+    quote_spanned! {span=>
+        #[doc(hidden)]
+        #[export_name = #link_sizeof]
+        unsafe extern "C" fn #local_sizeof() -> usize {
+            ::std::mem::size_of::<#elem>()
+        }
+    }
+}
+
 fn expand_rust_vec(elem: &RustName, types: &Types) -> TokenStream {
     let link_prefix = format!("cxxbridge1$rust_vec${}$", elem.to_symbol(types));
     let link_new = format!("{}new", link_prefix);
@@ -991,7 +1011,6 @@ fn expand_rust_vec(elem: &RustName, types: &Types) -> TokenStream {
     let link_data = format!("{}data", link_prefix);
     let link_reserve_total = format!("{}reserve_total", link_prefix);
     let link_set_len = format!("{}set_len", link_prefix);
-    let link_stride = format!("{}stride", link_prefix);
 
     let local_prefix = format_ident!("{}__vec_", elem.rust);
     let local_new = format_ident!("{}new", local_prefix);
@@ -1001,7 +1020,6 @@ fn expand_rust_vec(elem: &RustName, types: &Types) -> TokenStream {
     let local_data = format_ident!("{}data", local_prefix);
     let local_reserve_total = format_ident!("{}reserve_total", local_prefix);
     let local_set_len = format_ident!("{}set_len", local_prefix);
-    let local_stride = format_ident!("{}stride", local_prefix);
 
     let span = elem.span();
     quote_spanned! {span=>
@@ -1042,11 +1060,6 @@ fn expand_rust_vec(elem: &RustName, types: &Types) -> TokenStream {
         unsafe extern "C" fn #local_set_len(this: *mut ::cxx::private::RustVec<#elem>, len: usize) {
             (*this).set_len(len);
         }
-        #[doc(hidden)]
-        #[export_name = #link_stride]
-        unsafe extern "C" fn #local_stride() -> usize {
-            ::std::mem::size_of::<#elem>()
-        }
     }
 }
 
diff --git a/src/cxx.cc b/src/cxx.cc
index b1715832..700edbf3 100644
--- a/src/cxx.cc
+++ b/src/cxx.cc
@@ -436,6 +436,16 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
     ptr->~unique_ptr();                                                        \
   }
 
+#define RUST_SIZEOF_OPS(RUST_TYPE, CXX_TYPE)                                   \
+  template <>                                                                  \
+  size_t rust_sizeof<CXX_TYPE>() {                                             \
+    return sizeof(CXX_TYPE);                                                   \
+  }                                                                            \
+  template <>                                                                  \
+  size_t rust_sizeof<const CXX_TYPE>() {                                       \
+    return sizeof(CXX_TYPE);                                                   \
+  }                                                                            \
+
 #define RUST_VEC_EXTERNS(RUST_TYPE, CXX_TYPE)                                  \
   void cxxbridge1$rust_vec$##RUST_TYPE##$new(                                  \
       rust::Vec<CXX_TYPE> *ptr) noexcept;                                      \
@@ -450,8 +460,7 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
   void cxxbridge1$rust_vec$##RUST_TYPE##$reserve_total(                        \
       rust::Vec<CXX_TYPE> *ptr, std::size_t cap) noexcept;                     \
   void cxxbridge1$rust_vec$##RUST_TYPE##$set_len(rust::Vec<CXX_TYPE> *ptr,     \
-                                                 std::size_t len) noexcept;    \
-  std::size_t cxxbridge1$rust_vec$##RUST_TYPE##$stride() noexcept;
+                                                 std::size_t len) noexcept;
 
 #define RUST_VEC_OPS(RUST_TYPE, CXX_TYPE)                                      \
   template <>                                                                  \
@@ -482,10 +491,6 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
   void Vec<CXX_TYPE>::set_len(std::size_t len) noexcept {                      \
     cxxbridge1$rust_vec$##RUST_TYPE##$set_len(this, len);                      \
   }                                                                            \
-  template <>                                                                  \
-  std::size_t Vec<CXX_TYPE>::stride() noexcept {                               \
-    return cxxbridge1$rust_vec$##RUST_TYPE##$stride();                         \
-  }
 
 #define SHARED_PTR_OPS(RUST_TYPE, CXX_TYPE)                                    \
   static_assert(sizeof(std::shared_ptr<CXX_TYPE>) == 2 * sizeof(void *), "");  \
@@ -534,7 +539,7 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
   MACRO(isize, rust::isize)                                                    \
   MACRO(string, std::string)
 
-#define FOR_EACH_RUST_VEC(MACRO)                                               \
+#define FOR_EACH_RUST_BASE_TYPE(MACRO)                                         \
   FOR_EACH_NUMERIC(MACRO)                                                      \
   MACRO(bool, bool)                                                            \
   MACRO(char, char)                                                            \
@@ -548,12 +553,15 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
 
 extern "C" {
 FOR_EACH_STD_VECTOR(STD_VECTOR_OPS)
-FOR_EACH_RUST_VEC(RUST_VEC_EXTERNS)
 FOR_EACH_SHARED_PTR(SHARED_PTR_OPS)
+FOR_EACH_RUST_BASE_TYPE(RUST_VEC_EXTERNS)
 } // extern "C"
 
 namespace rust {
 inline namespace cxxbridge1 {
-FOR_EACH_RUST_VEC(RUST_VEC_OPS)
+FOR_EACH_RUST_BASE_TYPE(RUST_VEC_OPS)
+namespace detail {
+FOR_EACH_RUST_BASE_TYPE(RUST_SIZEOF_OPS)
+} // namespace detail
 } // namespace cxxbridge1
 } // namespace rust
diff --git a/src/symbols/rust_sizeof.rs b/src/symbols/rust_sizeof.rs
new file mode 100644
index 00000000..b1368a02
--- /dev/null
+++ b/src/symbols/rust_sizeof.rs
@@ -0,0 +1,41 @@
+use crate::rust_string::RustString;
+use alloc::vec::Vec;
+use core::mem;
+use std::os::raw::c_char;
+
+macro_rules! rust_stride_shims {
+    ($segment:expr, $ty:ty) => {
+        const_assert_eq!(mem::size_of::<[usize; 3]>(), mem::size_of::<Vec<$ty>>());
+        const_assert_eq!(mem::align_of::<usize>(), mem::align_of::<Vec<$ty>>());
+
+        const _: () = {
+            attr! {
+                #[export_name = concat!("cxxbridge1$rust_stride$", $segment)]
+                unsafe extern "C" fn __stride() -> usize {
+                    mem::size_of::<$ty>()
+                }
+            }
+        };
+    };
+}
+
+macro_rules! rust_stride_shims_for_primitive {
+    ($ty:ident) => {
+        rust_stride_shims!(stringify!($ty), $ty);
+    };
+}
+
+rust_stride_shims_for_primitive!(bool);
+rust_stride_shims_for_primitive!(u8);
+rust_stride_shims_for_primitive!(u16);
+rust_stride_shims_for_primitive!(u32);
+rust_stride_shims_for_primitive!(u64);
+rust_stride_shims_for_primitive!(i8);
+rust_stride_shims_for_primitive!(i16);
+rust_stride_shims_for_primitive!(i32);
+rust_stride_shims_for_primitive!(i64);
+rust_stride_shims_for_primitive!(f32);
+rust_stride_shims_for_primitive!(f64);
+
+rust_stride_shims!("char", c_char);
+rust_stride_shims!("string", RustString);
diff --git a/src/symbols/rust_vec.rs b/src/symbols/rust_vec.rs
index 6aa0b763..60b9cfa5 100644
--- a/src/symbols/rust_vec.rs
+++ b/src/symbols/rust_vec.rs
@@ -53,12 +53,6 @@ macro_rules! rust_vec_shims {
                     (*this).repr.set_len(len);
                 }
             }
-            attr! {
-                #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$stride")]
-                unsafe extern "C" fn __stride() -> usize {
-                    mem::size_of::<$ty>()
-                }
-            }
         };
     };
 }
diff --git a/syntax/check.rs b/syntax/check.rs
index 2ac800b7..f43c1e25 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -210,6 +210,7 @@ fn check_type_ref(cx: &mut Check, ty: &Ref) {
 fn check_type_slice_ref(cx: &mut Check, ty: &SliceRef) {
     let supported = match &ty.inner {
         Type::Str(_) | Type::SliceRef(_) => false,
+        Type::Ident(ident) => !is_opaque_cxx(cx, &ident.rust),
         element => !is_unsized(cx, element),
     };
 
@@ -217,9 +218,7 @@ fn check_type_slice_ref(cx: &mut Check, ty: &SliceRef) {
         let mutable = if ty.mutable { "mut " } else { "" };
         let mut msg = format!("unsupported &{}[T] element type", mutable);
         if let Type::Ident(ident) = &ty.inner {
-            if cx.types.rust.contains(&ident.rust) {
-                msg += ": opaque Rust type is not supported yet";
-            } else if is_opaque_cxx(cx, &ident.rust) {
+            if is_opaque_cxx(cx, &ident.rust) {
                 msg += ": opaque C++ type is not supported yet";
             }
         }


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 7504704b..da3a09f4 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -120,6 +120,8 @@ pub mod ffi {
         fn c_take_str(s: &str);
         fn c_take_slice_char(s: &[c_char]);
         fn c_take_slice_shared(s: &[Shared]);
+        fn c_take_slice_not_usized(s: &[Second]);
+        fn c_take_slice_not_usized_mut(s: &mut [Second]);
         fn c_take_rust_string(s: String);
         fn c_take_unique_ptr_string(s: UniquePtr<CxxString>);
         fn c_take_unique_ptr_vector_u8(v: UniquePtr<CxxVector<u8>>);
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index cd367d85..bf29f666 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -274,7 +274,19 @@ void c_take_slice_char(rust::Slice<const char> s) {
 }
 
 void c_take_slice_shared(rust::Slice<const Shared> s) {
-  if (s.size() == 2 && s.data()->z == 2020 && (s.data() + 1)->z == 2021) {
+  if (s.size() == 2 && s[0].z == 2020 && s[1].z == 2021) {
+    cxx_test_suite_set_correct();
+  }
+}
+
+void c_take_slice_not_usized(rust::Slice<const second::Second> s) {
+  if (s.size() == 2 && s[0].i == 1 && s[0].e == tests::COwnedEnum::CVAL1 && s[1].i == 2 && s[1].e == COwnedEnum::CVAL2) {
+    cxx_test_suite_set_correct();
+  }
+}
+
+void c_take_slice_not_usized_mut(rust::Slice<second::Second> s) {
+  if (s.size() == 2 && s[0].i == 1 && s[0].e == tests::COwnedEnum::CVAL1 && s[1].i == 2 && s[1].e == COwnedEnum::CVAL2) {
     cxx_test_suite_set_correct();
   }
 }
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 51ab51df..722ba75f 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -32,6 +32,10 @@ public:
 };
 } // namespace H
 
+namespace second {
+  struct Second;
+}
+
 namespace tests {
 
 struct R;
@@ -123,6 +127,8 @@ void c_take_ref_ns_c(const ::H::H &h);
 void c_take_str(rust::Str s);
 void c_take_slice_char(rust::Slice<const char> s);
 void c_take_slice_shared(rust::Slice<const Shared> s);
+void c_take_slice_not_usized(rust::Slice<const ::second::Second> s);
+void c_take_slice_not_usized_mut(rust::Slice<::second::Second> s);
 void c_take_rust_string(rust::String s);
 void c_take_unique_ptr_string(std::unique_ptr<std::string> s);
 void c_take_unique_ptr_vector_u8(std::unique_ptr<std::vector<uint8_t>> v);
diff --git a/tests/test.rs b/tests/test.rs
index 60bf1bdb..ab824e1b 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -125,6 +125,14 @@ fn test_c_take() {
         ffi::Shared { z: 2020 },
         ffi::Shared { z: 2021 },
     ]));
+    check!(ffi::c_take_slice_not_usized(&[
+        ffi::Second { i: 1, e: ffi::COwnedEnum::CVal1 },
+        ffi::Second { i: 2, e: ffi::COwnedEnum::CVal2 },
+    ]));
+    check!(ffi::c_take_slice_not_usized_mut(&mut [
+        ffi::Second { i: 1, e: ffi::COwnedEnum::CVal1 },
+        ffi::Second { i: 2, e: ffi::COwnedEnum::CVal2 },
+    ]));
     check!(ffi::c_take_rust_string("2020".to_owned()));
     check!(ffi::c_take_unique_ptr_string(
         ffi::c_return_unique_ptr_string()