namespace ffi_types { #define EMPTY_SLICE_BEGIN(T) reinterpret_cast(1) template T* _wrap_null(T* ptr) { return ptr ? ptr : reinterpret_cast(1); } // C++ std::ranges compatibility layer until C++17. #if __cpp_lib_ranges namespace ranges { using std::ranges::data; using std::ranges::size; } // namespace ranges #define SAFE_R R&& #else namespace ranges { template auto data(C& x) noexcept { auto begin = x.begin(); using iterator_type = decltype(begin); using pointer_type = decltype(&*begin); if constexpr (std::is_same_v) { // same type return begin; } else if constexpr (sizeof(iterator_type) == sizeof(uintptr_t)) { // likely to be a pointer return *reinterpret_cast(&begin); } else { if (begin != x.end()) { return &*begin; } else { return static_cast(nullptr); } } } template size_t size(C& x) noexcept { return x.end() - x.begin(); } } // namespace ranges // R&& is not safe without actual ranges #define SAFE_R const R& #endif struct CharStrRef; struct StrRef; struct CStrRef; struct CByteSliceRef; class BoxedStr; template struct CMutSliceRef; template struct CSliceRef; template struct CBoxedSlice; /// A minimal range type of slices to be compatible with internal ranges. template struct _SliceRange { T* _data; usize _size; auto* begin() const noexcept { return this->_data; } auto* end() const noexcept { return this->_data + _size; } }; /// Common C++ STL-like interface for &[T] and &str. /// @see std::span template typename I> struct _SliceInterface { // constants and types using element_type = T; using value_type = typename std::remove_cv_t; using size_type = uintptr_t; using difference_type = intptr_t; using pointer = element_type*; using const_pointer = const element_type*; using reference = element_type&; using const_reference = const element_type&; #if __cpp_lib_span using iterator = typename std::span::iterator; #else using iterator = pointer; #endif using reverse_iterator = std::reverse_iterator; // observers size_type size() const noexcept { return static_cast*>(this)->_size; } size_type size_bytes() const noexcept { return this->size() * sizeof(element_type); } bool empty() const noexcept { return this->size() == 0; } // element access reference operator[](size_type idx) const noexcept { assert(idx < this->size()); return this->data()[idx]; } reference front() const noexcept { return this->data()[0]; } reference back() const noexcept { return this->data()[size() - 1]; } pointer data() const noexcept { return static_cast*>(this)->_data; } // iterator #if __cpp_lib_span iterator begin() const noexcept { return span().begin(); } #else iterator begin() const noexcept { return data(); } #endif iterator end() const noexcept { return begin() + size(); } reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } _SliceRange get() const noexcept { return {data(), size()}; } #if __cpp_lib_span /// Returns the slice as a `std::span`. std::span span() const noexcept { return std::span(data(), size()); } #endif }; /// C++ counterpart of Rust `&mut [T]` and &[T]`. /// The interface is following `std::span` design. template struct MutSliceRef : public _SliceInterface { T* _data; usize _size; MutSliceRef() noexcept : _data(EMPTY_SLICE_BEGIN(T)), _size(0) {} MutSliceRef(const MutSliceRef&) = default; MutSliceRef(MutSliceRef&&) = default; explicit MutSliceRef(T* head, usize size) noexcept : _data(_wrap_null(head)), _size(size) {} template MutSliceRef(SAFE_R range) noexcept : _data(_wrap_null(ranges::data(range))), _size(static_cast(ranges::size(range))) {} MutSliceRef& operator=(const MutSliceRef&) = default; MutSliceRef& operator=(MutSliceRef&&) = default; CMutSliceRef into() const noexcept; /// Convert &[u8] to C++ string wrapper. /// This is &[u8] specialization as bytes template std::enable_if_t, CharStrRef> as_char_str() const noexcept; /// Convert &[u8] to Rust string wrapper without UTF-8 checking. /// This is &[u8] specialization as bytes /// /// @warning Safety: Only when the data is a valid UTF-8 string. template std::enable_if_t, StrRef> as_str_unchecked() const noexcept; }; static_assert(std::is_trivially_copyable>::value); static_assert(std::is_standard_layout>::value); /// SliceRef is a read-only slice `&[T]` in Rust and a conceptional alias to `MutSliceRef` in C++. /// This is not a hard alias because: /// - template alias doesn't support generic parameter deduction. /// - bindgen resolve template alias to the original type, which we don't supposed to do. template struct SliceRef : public MutSliceRef { SliceRef() noexcept : MutSliceRef(){}; SliceRef(const SliceRef&) = default; SliceRef(SliceRef&&) = default; explicit SliceRef(const T* head, usize size) noexcept : MutSliceRef(head, size) {} template SliceRef(SAFE_R range) noexcept : MutSliceRef(range) {} SliceRef& operator=(const SliceRef&) = default; SliceRef& operator=(SliceRef&&) = default; typename std::conditional, CByteSliceRef, CSliceRef>::type into() const noexcept; template static std::enable_if_t, SliceRef> from_buffer(const B& buffer) noexcept { return SliceRef(reinterpret_cast(&buffer), sizeof(B)); } }; static_assert(std::is_trivially_copyable>::value); static_assert(std::is_standard_layout>::value); /// C++ counterpart of `&[u8]` as alias for `SliceRef`. /// This alias is useful to pass `&[u8]` in cbindgen without configuration and boilerplate. using ByteSliceRef = SliceRef; template struct CMutSliceRef { CMutSliceRef() = _COPY_DELETE; CMutSliceRef(const CMutSliceRef&) = default; CMutSliceRef& operator=(const CMutSliceRef&) = default; #if _MSC_VER /// Creates a `CMutSliceRef` from a `MutSliceRef`. /// This is a workaround for MSVC constructor limitation. static CMutSliceRef from(const MutSliceRef& slice) noexcept { CMutSliceRef s; s._data = slice._data; s._size = slice._size; return s; } #else CMutSliceRef(const MutSliceRef& slice) noexcept { this->_data = slice._data; this->_size = slice._size; } /// Creates a `CMutSliceRef` from a `MutSliceRef`. /// This is a workaround for MSVC constructor limitation. static CMutSliceRef from(const MutSliceRef& slice) noexcept { return CMutSliceRef(slice); } #endif T* _data; usize _size; MutSliceRef operator()() const noexcept { return MutSliceRef(this->_data, this->_size); } }; static_assert(std::is_trivial>::value); static_assert(std::is_standard_layout>::value); template struct CSliceRef { CSliceRef() = _COPY_DELETE; CSliceRef(const CSliceRef&) = default; CSliceRef& operator=(const CSliceRef&) = default; #if _MSC_VER /// Creates a `CSliceRef` from a `SliceRef`. /// This is a workaround for MSVC constructor limitation. static CSliceRef from(const SliceRef& slice) noexcept { CSliceRef s; s._data = slice._data; s._size = slice._size; return s; } #else CSliceRef(const SliceRef& slice) noexcept { this->_data = slice._data; this->_size = slice._size; } /// Creates a `CSliceRef` from a `SliceRef`. /// This is a workaround for MSVC constructor limitation. static CSliceRef from(const SliceRef& slice) noexcept { return CSliceRef(slice); } #endif const T* _data; usize _size; SliceRef operator()() const noexcept { return SliceRef(this->_data, this->_size); } }; static_assert(std::is_trivial>::value); static_assert(std::is_standard_layout>::value); // Alias is not a C type in MSVC, so this line doesn't work. // ```c++ // using CByteSliceRef = CSliceRef; // ``` struct CByteSliceRef { CByteSliceRef() = _COPY_DELETE; CByteSliceRef(const CByteSliceRef&) = default; CByteSliceRef& operator=(const CByteSliceRef&) = default; #if _MSC_VER /// Creates a `CByteSliceRef` from a `ByteSliceRef`. /// This is a workaround for MSVC constructor limitation. static CByteSliceRef from(const ByteSliceRef& slice) noexcept { CByteSliceRef s; s._data = slice._data; s._size = slice._size; return s; } #else CByteSliceRef(const ByteSliceRef& slice) noexcept { this->_data = slice._data; this->_size = slice._size; } /// Creates a `CByteSliceRef` from a `ByteSliceRef`. /// This is a workaround for MSVC constructor limitation. static CByteSliceRef from(const ByteSliceRef& slice) noexcept { return CByteSliceRef(slice); } #endif const uint8_t* _data; usize _size; ByteSliceRef operator()() const noexcept { ByteSliceRef slice; slice._data = this->_data; slice._size = this->_size; return slice; } }; static_assert(std::is_trivial::value); static_assert(std::is_standard_layout::value); /// C++ counterpart of `Box` /// The ownership API is following `std::unique_ptr` design and the slice API is following `std::span` design. template class BoxedSlice : public MutSliceRef { public: BoxedSlice() = delete; BoxedSlice(const BoxedSlice&) = delete; BoxedSlice(BoxedSlice&& s) noexcept : MutSliceRef(s) { s._data = EMPTY_SLICE_BEGIN(T); s._size = 0; } BoxedSlice(std::nullptr_t) noexcept : MutSliceRef() {} ~BoxedSlice() noexcept { if (this->_size > 0) { this->_drop(); } } BoxedSlice& operator=(BoxedSlice&& b) noexcept { this->reset(b.release()); return *this; } void _drop() noexcept; CBoxedSlice into() noexcept; const CBoxedSlice& as_c() const noexcept { return *reinterpret_cast*>(this); } CBoxedSlice& as_c() noexcept { return *reinterpret_cast*>(this); } void reset(_SliceRange s) noexcept { if (this->_size > 0) { this->_drop(); } this->_data = s._data; this->_size = s._size; } _SliceRange release() noexcept { const auto range = this->get(); this->_data = EMPTY_SLICE_BEGIN(T); this->_size = 0; return range; } /// Returns a mutable slice of the boxed slice. auto as_slice() noexcept { return MutSliceRef(this->_data, this->_size); } /// Returns a read-only slice of the boxed slice. auto as_slice() const noexcept { return SliceRef(this->_data, this->_size); } }; static_assert(sizeof(usize) * 2 == sizeof(BoxedSlice)); static_assert(std::is_standard_layout>::value); /// C++ wrapper for a boxed slice with C ABI compatible layout. /// /// @warning This type does *NOT* implement a safe destructor. /// To avoid leak, see general note about C-prefixed types at the top of module documentation. template struct [[nodiscard]] CBoxedSlice { CBoxedSlice() = _COPY_DELETE; CBoxedSlice(const CBoxedSlice&) = _COPY_DELETE; CBoxedSlice& operator=(const CBoxedSlice&) = _COPY_DELETE; #if _MSC_VER /// This is a workaround for MSVC constructor limitation. static CBoxedSlice from(BoxedSlice&& slice) noexcept { auto r = slice.release(); CBoxedSlice s; s._data = r._data; s._size = r._size; return s; } #else CBoxedSlice(CBoxedSlice&&) = default; CBoxedSlice& operator=(CBoxedSlice&&) = default; /// Constructs a `CBoxedSlice` by moving a `BoxedSlice`. CBoxedSlice(BoxedSlice&& slice) noexcept { auto r = slice.release(); this->_data = r._data; this->_size = r._size; } /// This is a workaround for MSVC constructor limitation. static CBoxedSlice from(BoxedSlice&& slice) noexcept { return CBoxedSlice(std::move(slice)); } #endif T* _data; usize _size; /// Conversion operator to `BoxedSlice`. /// /// @note This conversion *MUST* be called unless the value is going to be passed to Rust side. BoxedSlice operator()() noexcept { auto slice = BoxedSlice(nullptr); auto r = this->release(); slice._data = r._data; slice._size = r._size; return slice; } _SliceRange get() const noexcept { return {this->_data, this->_size}; } _SliceRange release() noexcept { const auto range = this->get(); this->_data = EMPTY_SLICE_BEGIN(T); this->_size = 0; return range; } }; static_assert(std::is_trivial>::value); static_assert(std::is_standard_layout>::value); /// C++ unsafe counterpart of Rust `&str`. /// /// Because `StrRef` in C++ side doesn't have any UTF-8 validation checking, /// `CharStrRef` is an alternative of `StrRef` creation in C++ side. /// The value can be converted to either `&[u8]` or `&str` with UTF-8 validation in Rust side. struct CharStrRef { CharStrRef() = _COPY_DELETE; CharStrRef(const CharStrRef&) = default; CharStrRef& operator=(const CharStrRef&) = default; // #if !_MSC_VER CharStrRef(const char* head, usize size) noexcept : _data(_wrap_null(head)), _size(size) {} template CharStrRef(SAFE_R range) noexcept : _data(_wrap_null(ranges::data(range))), _size(ranges::size(range)) {} CharStrRef(std::nullptr_t) noexcept : _data(EMPTY_SLICE_BEGIN(const char)), _size(0) {} // #endif const char* _data; usize _size; // constants and types using element_type = const char; using value_type = typename std::remove_cv_t; using size_type = uintptr_t; using difference_type = intptr_t; using pointer = element_type*; using const_pointer = const element_type*; using reference = element_type&; using const_reference = const element_type&; using iterator = typename std::string_view::iterator; using reverse_iterator = std::reverse_iterator; // observers size_type size() const noexcept { return this->_size; } size_type size_bytes() const noexcept { return this->size() * sizeof(element_type); } bool empty() const noexcept { return this->size() == 0; } // element access reference operator[](size_type idx) const noexcept { assert(idx < this->size()); return this->data()[idx]; } reference front() const noexcept { return this->data()[0]; } reference back() const noexcept { return this->data()[size() - 1]; } pointer data() const noexcept { return this->_data; } // iterator iterator begin() const noexcept { return view().begin(); } iterator end() const noexcept { return begin() + size(); } reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } _SliceRange get() const noexcept { return {data(), size()}; } #if __cpp_lib_span /// Returns the slice as a `std::span`. std::span span() const noexcept { return std::span(data(), size()); } #endif StrRef as_str_unchecked() const noexcept; #if __cpp_lib_string_view /// Constructs a `CharStrRef` from a null-terminated string. CharStrRef(const char* s) noexcept : CharStrRef(std::string_view(s)) {} /// Returns a `std::string_view` of the string. std::string_view view() const noexcept { return std::string_view(this->data(), this->size()); } /// Conversion operator to `std::string_view`. operator std::string_view() const noexcept { return this->view(); } /// Create a `std::string` by copying data. /// @return The newly created `std::string`. std::string to_string() const noexcept { return std::string(this->view()); } #endif }; static_assert(sizeof(SliceRef) == sizeof(CharStrRef)); static_assert(std::is_trivial::value); static_assert(std::is_standard_layout::value); /// C++ counterpart of Rust `&str`. /// /// Because `StrRef` in C++ side doesn't have any UTF-8 validation checking, /// this type is highly recommended to be created from Rust side. /// /// To create `StrRef` in C++ side, use `ByteSliceRef::as_str_unchecked()` or `CharStrRef::as_str_unchecked()` /// at your own risk. struct StrRef : public CharStrRef { StrRef() = delete; StrRef(const StrRef&) = default; StrRef(const BoxedStr&) noexcept; StrRef(std::nullptr_t) noexcept : CharStrRef(EMPTY_SLICE_BEGIN(const char), 0) {} StrRef& operator=(const StrRef&) = default; }; static_assert(std::is_trivially_copyable::value); static_assert(std::is_standard_layout::value); /// C++ wrapper for a str with C ABI compatible layout. struct CStrRef { CStrRef() = _COPY_DELETE; CStrRef(const CStrRef&) = default; CStrRef& operator=(const CStrRef&) = default; #if !_MSC_VER CStrRef(const StrRef& slice) noexcept { this->_data = slice.data(); this->_size = slice.size(); } #endif const char* _data; usize _size; StrRef operator()() const noexcept { auto s = StrRef(nullptr); s._data = this->_data; s._size = this->_size; return s; } }; static_assert(std::is_trivial::value); static_assert(std::is_standard_layout::value); /// C++ counterpart of Rust `Box`. class BoxedStr : public StrRef { public: BoxedStr() = delete; BoxedStr(BoxedStr&) = delete; BoxedStr(BoxedStr&& s) noexcept : StrRef(s) { s._data = EMPTY_SLICE_BEGIN(const char); s._size = 0; } BoxedStr(std::nullptr_t) noexcept : StrRef(nullptr) {} ~BoxedStr() noexcept { if (this->_size > 0) { this->_drop(); } } BoxedStr& operator=(BoxedStr&& b) noexcept { this->reset(b.release()); return *this; } void _drop() noexcept; void reset(_SliceRange s) noexcept { if (this->_size > 0) { this->_drop(); } this->_data = s._data; this->_size = s._size; } _SliceRange release() noexcept { const auto range = this->get(); this->_data = EMPTY_SLICE_BEGIN(const char); this->_size = 0; return range; } StrRef as_str() const noexcept { return this->as_str_unchecked(); } }; static_assert(sizeof(StrRef) == sizeof(BoxedStr)); static_assert(std::is_standard_layout::value); /// C++ wrapper for Rust `Box` with C ABI compatible layout. /// /// @warning This type does *NOT* implement a safe destructor. /// To avoid leak, see general note about C-prefixed types at the top of module documentation. struct [[nodiscard]] CBoxedStr { CBoxedStr() = _COPY_DELETE; CBoxedStr(const CBoxedStr&) = _COPY_DELETE; CBoxedStr& operator=(const CBoxedStr&) = _COPY_DELETE; const char* _data; usize _size; #if _MSC_VER /// This is a workaround for MSVC constructor limitation. static CBoxedStr from(BoxedStr&& str) noexcept { auto r = str.release(); CBoxedStr s; s._data = r._data; s._size = r._size; return s; } #else CBoxedStr(CBoxedStr&&) = default; CBoxedStr& operator=(CBoxedStr&&) = default; CBoxedStr(BoxedStr&& str) noexcept { auto r = str.release(); this->_data = r._data; this->_size = r._size; } /// This is a workaround for MSVC constructor limitation. static CBoxedStr from(BoxedStr&& slice) noexcept { return CBoxedStr(std::move(slice)); } #endif BoxedStr operator()() noexcept { auto slice = BoxedStr(nullptr); auto r = this->release(); slice._data = r._data; slice._size = r._size; return slice; } _SliceRange release() noexcept { const auto range = _SliceRange{_data, _size}; this->_data = EMPTY_SLICE_BEGIN(const char); this->_size = 0; return range; } }; static_assert(std::is_trivial::value); static_assert(std::is_standard_layout::value); template inline CMutSliceRef MutSliceRef::into() const noexcept { return CMutSliceRef::from(*this); } template inline typename std::conditional, CByteSliceRef, CSliceRef>::type SliceRef::into() const noexcept { if constexpr (std::is_same_v) { return CByteSliceRef::from(*this); } else { return CSliceRef::from(*this); } } template inline CBoxedSlice BoxedSlice::into() noexcept { return CBoxedSlice::from(std::move(*this)); } inline StrRef::StrRef(const BoxedStr& owned) noexcept : CharStrRef(owned._data, owned._size) {} inline StrRef CharStrRef::as_str_unchecked() const noexcept { return *reinterpret_cast(this); } template <> template <> inline CharStrRef MutSliceRef::as_char_str() const noexcept { return CharStrRef(reinterpret_cast(this->_data), this->_size); } template <> template <> inline StrRef MutSliceRef::as_str_unchecked() const noexcept { return as_char_str().as_str_unchecked(); } #undef SAFE_R #undef EMPTY_SLICE_BEGIN } // namespace ffi_types