Repository: dtolnay/cxx
Pull request #906 (2021-07-31 13:19:43)

Add UTF-16 rust::String initialization
This patch exposes String::from_utf16 to the C++ interface for rust::String.


Patch:
diff --git a/include/cxx.h b/include/cxx.h
index 1153e437..3dab92e8 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -43,6 +43,8 @@ public:
   String(const std::string &);
   String(const char *);
   String(const char *, std::size_t);
+  String(const char16_t *);
+  String(const char16_t *, std::size_t);
 
   String &operator=(const String &) &noexcept;
   String &operator=(String &&) &noexcept;
diff --git a/src/cxx.cc b/src/cxx.cc
index ecfc1c69..4080ad66 100644
--- a/src/cxx.cc
+++ b/src/cxx.cc
@@ -31,8 +31,10 @@ void cxxbridge1$cxx_string$push(std::string &s, const std::uint8_t *ptr,
 void cxxbridge1$string$new(rust::String *self) noexcept;
 void cxxbridge1$string$clone(rust::String *self,
                              const rust::String &other) noexcept;
-bool cxxbridge1$string$from(rust::String *self, const char *ptr,
-                            std::size_t len) noexcept;
+bool cxxbridge1$string$from_utf8(rust::String *self, const char *ptr,
+                                 std::size_t len) noexcept;
+bool cxxbridge1$string$from_utf16(rust::String *self, const char16_t *ptr,
+                                  std::size_t len) noexcept;
 void cxxbridge1$string$drop(rust::String *self) noexcept;
 const char *cxxbridge1$string$ptr(const rust::String *self) noexcept;
 std::size_t cxxbridge1$string$len(const rust::String *self) noexcept;
@@ -81,11 +83,17 @@ String::String(String &&other) noexcept : repr(other.repr) {
 String::~String() noexcept { cxxbridge1$string$drop(this); }
 
 static void initString(String *self, const char *s, std::size_t len) {
-  if (!cxxbridge1$string$from(self, s, len)) {
+  if (!cxxbridge1$string$from_utf8(self, s, len)) {
     panic<std::invalid_argument>("data for rust::String is not utf-8");
   }
 }
 
+static void initString(String *self, const char16_t *s, std::size_t len) {
+  if (!cxxbridge1$string$from_utf16(self, s, len)) {
+    panic<std::invalid_argument>("data for rust::String is not utf-16");
+  }
+}
+
 String::String(const std::string &s) { initString(this, s.data(), s.length()); }
 
 String::String(const char *s) {
@@ -100,6 +108,19 @@ String::String(const char *s, std::size_t len) {
              len);
 }
 
+String::String(const char16_t *s) {
+  assert(s != nullptr);
+  initString(this, s, std::char_traits<char16_t>::length(s));
+}
+
+String::String(const char16_t *s, std::size_t len) {
+  assert(s != nullptr || len == 0);
+  initString(this,
+             s == nullptr && len == 0 ? reinterpret_cast<const char16_t *>(2)
+                                      : s,
+             len);
+}
+
 String &String::operator=(const String &other) &noexcept {
   if (this != &other) {
     cxxbridge1$string$drop(this);
diff --git a/src/symbols/rust_string.rs b/src/symbols/rust_string.rs
index 91fd78a3..29bcf85d 100644
--- a/src/symbols/rust_string.rs
+++ b/src/symbols/rust_string.rs
@@ -15,8 +15,8 @@ unsafe extern "C" fn string_clone(this: &mut MaybeUninit<String>, other: &String
     ptr::write(this.as_mut_ptr(), other.clone());
 }
 
-#[export_name = "cxxbridge1$string$from"]
-unsafe extern "C" fn string_from(
+#[export_name = "cxxbridge1$string$from_utf8"]
+unsafe extern "C" fn string_from_utf8(
     this: &mut MaybeUninit<String>,
     ptr: *const u8,
     len: usize,
@@ -31,6 +31,22 @@ unsafe extern "C" fn string_from(
     }
 }
 
+#[export_name = "cxxbridge1$string$from_utf16"]
+unsafe extern "C" fn string_from_utf16(
+    this: &mut MaybeUninit<String>,
+    ptr: *const u16,
+    len: usize,
+) -> bool {
+    let slice = slice::from_raw_parts(ptr, len);
+    match String::from_utf16(slice) {
+        Ok(s) => {
+            ptr::write(this.as_mut_ptr(), s);
+            true
+        }
+        Err(_) => false,
+    }
+}
+
 #[export_name = "cxxbridge1$string$drop"]
 unsafe extern "C" fn string_drop(this: &mut ManuallyDrop<String>) {
     ManuallyDrop::drop(this);


Test patch:
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index ff215ca0..d153cd16 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -841,6 +841,12 @@ extern "C" const char *cxx_run_test() noexcept {
   ASSERT(cstr == "foo");
   ASSERT(other_cstr == "test");
 
+  const char *utf8_literal = u8"Test string";
+  const char16_t *utf16_literal = u"Test string";
+  rust::String utf8_rstring = utf8_literal;
+  rust::String utf16_rstring = utf16_literal;
+  ASSERT(utf8_rstring == utf16_rstring);
+
   rust::Vec<int> vec1{1, 2};
   rust::Vec<int> vec2{3, 4};
   swap(vec1, vec2);