Repository: dtolnay/cxx
Pull request #984 (2021-12-24 17:59:31)

Add String::lossy constructors to C++ API
This constructor maps to `String::from_utf8_lossy` or
`String::from_utf16_lossy`. It's useful in situations where producing a
slightly garbled string on invalid Unicode data is preferable to
crashing the process, e.g. when passing error messages from C++ to Rust.


Patch:
diff --git a/book/src/binding/string.md b/book/src/binding/string.md
index d564e00c..ad5e9caa 100644
--- a/book/src/binding/string.md
+++ b/book/src/binding/string.md
@@ -18,15 +18,24 @@ public:
   String(String &&) noexcept;
   ~String() noexcept;
 
-  // Throws std::invalid_argument if not utf-8.
+  // Throws std::invalid_argument if not UTF-8.
   String(const std::string &);
   String(const char *);
   String(const char *, size_t);
 
-  // Throws std::invalid_argument if not utf-16.
+  // Replaces invalid UTF-8 data with the replacement character (U+FFFD).
+  static String lossy(const std::string &) noexcept;
+  static String lossy(const char *) noexcept;
+  static String lossy(const char *, std::size_t) noexcept;
+
+  // Throws std::invalid_argument if not UTF-16.
   String(const char16_t *);
   String(const char16_t *, size_t);
 
+  // Replaces invalid UTF-16 data with the replacement character (U+FFFD).
+  static String lossy(const char16_t *) noexcept;
+  static String lossy(const char16_t *, std::size_t) noexcept;
+
   String &operator=(const String &) noexcept;
   String &operator=(String &&) noexcept;
 
diff --git a/book/src/binding/vec.md b/book/src/binding/vec.md
index 4d6587ab..a02b93c7 100644
--- a/book/src/binding/vec.md
+++ b/book/src/binding/vec.md
@@ -47,7 +47,6 @@ public:
   void push_back(T &&value);
   template <typename... Args>
   void emplace_back(Args &&...args);
-  void truncate(size_t len);
   void clear();
 
   class iterator;
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 43e2d2fa..64d83110 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -1475,11 +1475,6 @@ fn write_rust_vec_extern(out: &mut OutFile, key: NamedImplKey) {
         "void cxxbridge1$rust_vec${}$set_len(::rust::Vec<{}> *ptr, ::std::size_t len) noexcept;",
         instance, inner,
     );
-    writeln!(
-        out,
-        "void cxxbridge1$rust_vec${}$truncate(::rust::Vec<{}> *ptr, ::std::size_t len) noexcept;",
-        instance, inner,
-    );
     writeln!(
         out,
         "void cxxbridge1$rust_vec${}$clear(::rust::Vec<{}> *ptr) noexcept;",
@@ -1596,16 +1591,6 @@ fn write_rust_vec_impl(out: &mut OutFile, key: NamedImplKey) {
     );
     writeln!(out, "}}");
 
-    writeln!(out, "template <>");
-    begin_function_definition(out);
-    writeln!(out, "void Vec<{}>::truncate(::std::size_t len) {{", inner,);
-    writeln!(
-        out,
-        "  return cxxbridge1$rust_vec${}$truncate(this, len);",
-        instance,
-    );
-    writeln!(out, "}}");
-
     writeln!(out, "template <>");
     begin_function_definition(out);
     writeln!(out, "void Vec<{}>::clear() {{", inner);
diff --git a/include/cxx.h b/include/cxx.h
index 857247cb..aff2f5dc 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -24,6 +24,7 @@ namespace rust {
 inline namespace cxxbridge1 {
 
 struct unsafe_bitcopy_t;
+struct lossy_t;
 
 namespace {
 template <typename T>
@@ -46,6 +47,13 @@ public:
   String(const char16_t *);
   String(const char16_t *, std::size_t);
 
+  // Replace invalid Unicode data with the replacement character (U+FFFD).
+  static String lossy(const std::string &) noexcept;
+  static String lossy(const char *) noexcept;
+  static String lossy(const char *, std::size_t) noexcept;
+  static String lossy(const char16_t *) noexcept;
+  static String lossy(const char16_t *, std::size_t) noexcept;
+
   String &operator=(const String &) &noexcept;
   String &operator=(String &&) &noexcept;
 
@@ -85,6 +93,8 @@ public:
   String(unsafe_bitcopy_t, const String &) noexcept;
 
 private:
+  String(lossy_t, const char *, std::size_t) noexcept;
+  String(lossy_t, const char16_t *, std::size_t) noexcept;
   friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); }
 
   // Size and alignment statically verified by rust_string.rs.
@@ -324,7 +334,6 @@ public:
   void push_back(T &&value);
   template <typename... Args>
   void emplace_back(Args &&...args);
-  void truncate(std::size_t len);
   void clear();
 
   using iterator = typename Slice<T>::iterator;
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 8c1389bc..1e3c8da6 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -1288,7 +1288,6 @@ fn expand_rust_vec(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl
     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_truncate = format!("{}truncate", link_prefix);
     let link_clear = format!("{}clear", link_prefix);
 
     let local_prefix = format_ident!("{}__vec_", elem);
@@ -1299,7 +1298,6 @@ fn expand_rust_vec(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl
     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_truncate = format_ident!("{}truncate", local_prefix);
     let local_clear = format_ident!("{}clear", local_prefix);
 
     let (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve);
@@ -1355,12 +1353,6 @@ fn expand_rust_vec(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl
             (*this).set_len(len);
         }
         #[doc(hidden)]
-        #[export_name = #link_truncate]
-        unsafe extern "C" fn #local_truncate #impl_generics(this: *mut ::cxx::private::RustVec<#elem #ty_generics>, len: usize) {
-            let __fn = concat!("<", module_path!(), #prevent_unwind_drop_label);
-            ::cxx::private::prevent_unwind(__fn, || (*this).truncate(len));
-        }
-        #[doc(hidden)]
         #[export_name = #link_clear]
         unsafe extern "C" fn #local_clear #impl_generics(this: *mut ::cxx::private::RustVec<#elem #ty_generics>) {
             let __fn = concat!("<", module_path!(), #prevent_unwind_drop_label);
diff --git a/src/cxx.cc b/src/cxx.cc
index 7fffa834..983e38fe 100644
--- a/src/cxx.cc
+++ b/src/cxx.cc
@@ -40,8 +40,12 @@ void cxxbridge1$string$clone(rust::String *self,
                              const rust::String &other) noexcept;
 bool cxxbridge1$string$from_utf8(rust::String *self, const char *ptr,
                                  std::size_t len) noexcept;
+void cxxbridge1$string$from_utf8_lossy(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$from_utf16_lossy(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;
@@ -69,6 +73,8 @@ std::size_t cxxbridge1$slice$len(const void *self) noexcept;
 namespace rust {
 inline namespace cxxbridge1 {
 
+struct lossy_t {};
+
 template <typename Exception>
 void panic [[noreturn]] (const char *msg) {
 #if defined(RUST_CXX_NO_EXCEPTIONS)
@@ -132,6 +138,43 @@ String::String(const char16_t *s, std::size_t len) {
              len);
 }
 
+String::String(lossy_t, const char *s, std::size_t len) noexcept {
+  cxxbridge1$string$from_utf8_lossy(
+      this, s == nullptr && len == 0 ? reinterpret_cast<const char *>(1) : s,
+      len);
+}
+
+String::String(lossy_t, const char16_t *s, std::size_t len) noexcept {
+  cxxbridge1$string$from_utf16_lossy(
+      this,
+      s == nullptr && len == 0 ? reinterpret_cast<const char16_t *>(2) : s,
+      len);
+}
+
+String String::lossy(const std::string &s) noexcept {
+  return String::lossy(s.data(), s.length());
+}
+
+String String::lossy(const char *s) noexcept {
+  assert(s != nullptr);
+  return String::lossy(s, std::strlen(s));
+}
+
+String String::lossy(const char *s, std::size_t len) noexcept {
+  assert(s != nullptr || len == 0);
+  return String(lossy_t{}, s, len);
+}
+
+String String::lossy(const char16_t *s) noexcept {
+  assert(s != nullptr);
+  return String::lossy(s, std::char_traits<char16_t>::length(s));
+}
+
+String String::lossy(const char16_t *s, std::size_t len) noexcept {
+  assert(s != nullptr || len == 0);
+  return String(lossy_t{}, s, len);
+}
+
 String &String::operator=(const String &other) &noexcept {
   if (this != &other) {
     cxxbridge1$string$drop(this);
@@ -569,8 +612,6 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
       rust::Vec<CXX_TYPE> *ptr, std::size_t new_cap) noexcept;                 \
   void cxxbridge1$rust_vec$##RUST_TYPE##$set_len(rust::Vec<CXX_TYPE> *ptr,     \
                                                  std::size_t len) noexcept;    \
-  void cxxbridge1$rust_vec$##RUST_TYPE##$truncate(rust::Vec<CXX_TYPE> *ptr,    \
-                                                  std::size_t len) noexcept;   \
   void cxxbridge1$rust_vec$##RUST_TYPE##$clear(                                \
       rust::Vec<CXX_TYPE> *ptr) noexcept;
 
@@ -604,10 +645,6 @@ static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *),
     cxxbridge1$rust_vec$##RUST_TYPE##$set_len(this, len);                      \
   }                                                                            \
   template <>                                                                  \
-  void Vec<CXX_TYPE>::truncate(std::size_t len) {                              \
-    cxxbridge1$rust_vec$##RUST_TYPE##$truncate(this, len);                     \
-  }                                                                            \
-  template <>                                                                  \
   void Vec<CXX_TYPE>::clear() {                                                \
     cxxbridge1$rust_vec$##RUST_TYPE##$clear(this);                             \
   }
diff --git a/src/rust_vec.rs b/src/rust_vec.rs
index d471be6d..47d49a7b 100644
--- a/src/rust_vec.rs
+++ b/src/rust_vec.rs
@@ -69,10 +69,6 @@ impl<T> RustVec<T> {
         unsafe { self.as_mut_vec().set_len(len) }
     }
 
-    pub fn truncate(&mut self, len: usize) {
-        self.as_mut_vec().truncate(len)
-    }
-
     pub fn clear(&mut self) {
         self.as_mut_vec().clear();
     }
diff --git a/src/symbols/rust_string.rs b/src/symbols/rust_string.rs
index 115ffe3d..8b7c8c48 100644
--- a/src/symbols/rust_string.rs
+++ b/src/symbols/rust_string.rs
@@ -39,6 +39,18 @@ unsafe extern "C" fn string_from_utf8(
     }
 }
 
+#[export_name = "cxxbridge1$string$from_utf8_lossy"]
+unsafe extern "C" fn string_from_utf8_lossy(
+    this: &mut MaybeUninit<String>,
+    ptr: *const u8,
+    len: usize,
+) {
+    let slice = unsafe { slice::from_raw_parts(ptr, len) };
+    let owned = String::from_utf8_lossy(slice).into_owned();
+    let this = this.as_mut_ptr();
+    unsafe { ptr::write(this, owned) }
+}
+
 #[export_name = "cxxbridge1$string$from_utf16"]
 unsafe extern "C" fn string_from_utf16(
     this: &mut MaybeUninit<String>,
@@ -56,6 +68,18 @@ unsafe extern "C" fn string_from_utf16(
     }
 }
 
+#[export_name = "cxxbridge1$string$from_utf16_lossy"]
+unsafe extern "C" fn string_from_utf16_lossy(
+    this: &mut MaybeUninit<String>,
+    ptr: *const u16,
+    len: usize,
+) {
+    let slice = unsafe { slice::from_raw_parts(ptr, len) };
+    let owned = String::from_utf16_lossy(slice);
+    let this = this.as_mut_ptr();
+    unsafe { ptr::write(this, owned) }
+}
+
 #[export_name = "cxxbridge1$string$drop"]
 unsafe extern "C" fn string_drop(this: &mut ManuallyDrop<String>) {
     unsafe { ManuallyDrop::drop(this) }
diff --git a/src/symbols/rust_vec.rs b/src/symbols/rust_vec.rs
index 221e087c..4613db3a 100644
--- a/src/symbols/rust_vec.rs
+++ b/src/symbols/rust_vec.rs
@@ -56,12 +56,6 @@ macro_rules! rust_vec_shims {
                     unsafe { (*this).set_len(len) }
                 }
             }
-            attr! {
-                #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$truncate")]
-                unsafe extern "C" fn __truncate(this: *mut RustVec<$ty>, len: usize) {
-                    unsafe { (*this).truncate(len) }
-                }
-            }
             attr! {
                 #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$clear")]
                 unsafe extern "C" fn __clear(this: *mut RustVec<$ty>) {


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 98689702..2af771c0 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -103,6 +103,7 @@ pub mod ffi {
         fn c_return_slice_char(shared: &Shared) -> &[c_char];
         fn c_return_mutsliceu8(slice: &mut [u8]) -> &mut [u8];
         fn c_return_rust_string() -> String;
+        fn c_return_rust_string_lossy() -> String;
         fn c_return_unique_ptr_string() -> UniquePtr<CxxString>;
         fn c_return_unique_ptr_vector_u8() -> UniquePtr<CxxVector<u8>>;
         fn c_return_unique_ptr_vector_f64() -> UniquePtr<CxxVector<f64>>;
@@ -149,7 +150,6 @@ pub mod ffi {
         fn c_take_rust_vec_index(v: Vec<u8>);
         fn c_take_rust_vec_shared_index(v: Vec<Shared>);
         fn c_take_rust_vec_shared_push(v: Vec<Shared>);
-        fn c_take_rust_vec_shared_truncate(v: Vec<Shared>);
         fn c_take_rust_vec_shared_clear(v: Vec<Shared>);
         fn c_take_rust_vec_shared_forward_iterator(v: Vec<Shared>);
         fn c_take_rust_vec_shared_sort(v: Vec<Shared>);
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index ac7738df..99c5c62f 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -110,6 +110,10 @@ rust::Slice<uint8_t> c_return_mutsliceu8(rust::Slice<uint8_t> slice) {
 
 rust::String c_return_rust_string() { return "2020"; }
 
+rust::String c_return_rust_string_lossy() {
+  return rust::String::lossy("Hello \xf0\x90\x80World");
+}
+
 std::unique_ptr<std::string> c_return_unique_ptr_string() {
   return std::unique_ptr<std::string>(new std::string("2020"));
 }
@@ -452,16 +456,6 @@ void c_take_rust_vec_shared_push(rust::Vec<Shared> v) {
   }
 }
 
-void c_take_rust_vec_shared_truncate(rust::Vec<Shared> v) {
-  v.truncate(1);
-  if (v.size() == 1) {
-    v.truncate(0);
-    if (v.size() == 0) {
-      cxx_test_suite_set_correct();
-    }
-  }
-}
-
 void c_take_rust_vec_shared_clear(rust::Vec<Shared> v) {
   v.clear();
   if (v.size() == 0) {
@@ -870,6 +864,12 @@ extern "C" const char *cxx_run_test() noexcept {
   rust::String utf16_rstring = utf16_literal;
   ASSERT(utf8_rstring == utf16_rstring);
 
+  const char *bad_utf8_literal = "test\x80";
+  const char16_t *bad_utf16_literal = u"test\xDD1E";
+  rust::String bad_utf8_rstring = rust::String::lossy(bad_utf8_literal);
+  rust::String bad_utf16_rstring = rust::String::lossy(bad_utf16_literal);
+  ASSERT(bad_utf8_rstring == bad_utf16_rstring);
+
   rust::Vec<int> vec1{1, 2};
   rust::Vec<int> vec2{3, 4};
   swap(vec1, vec2);
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 13e9c0f7..ea5c6c32 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -100,6 +100,7 @@ rust::Str c_return_str(const Shared &shared);
 rust::Slice<const char> c_return_slice_char(const Shared &shared);
 rust::Slice<uint8_t> c_return_mutsliceu8(rust::Slice<uint8_t> slice);
 rust::String c_return_rust_string();
+rust::String c_return_rust_string_lossy();
 std::unique_ptr<std::string> c_return_unique_ptr_string();
 std::unique_ptr<std::vector<uint8_t>> c_return_unique_ptr_vector_u8();
 std::unique_ptr<std::vector<double>> c_return_unique_ptr_vector_f64();
@@ -152,7 +153,6 @@ void c_take_rust_vec_nested_ns_shared(rust::Vec<::A::B::ABShared> v);
 void c_take_rust_vec_string(rust::Vec<rust::String> v);
 void c_take_rust_vec_shared_index(rust::Vec<Shared> v);
 void c_take_rust_vec_shared_push(rust::Vec<Shared> v);
-void c_take_rust_vec_shared_truncate(rust::Vec<Shared> v);
 void c_take_rust_vec_shared_clear(rust::Vec<Shared> v);
 void c_take_rust_vec_shared_forward_iterator(rust::Vec<Shared> v);
 void c_take_rust_vec_shared_sort(rust::Vec<Shared> v);
diff --git a/tests/test.rs b/tests/test.rs
index 8279b752..735f7e5d 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -51,6 +51,7 @@ fn test_c_return() {
         cast::c_char_to_unsigned(ffi::c_return_slice_char(&shared)),
     );
     assert_eq!("2020", ffi::c_return_rust_string());
+    assert_eq!("Hello �World", ffi::c_return_rust_string_lossy());
     assert_eq!("2020", ffi::c_return_unique_ptr_string().to_str().unwrap());
     assert_eq!(4, ffi::c_return_unique_ptr_vector_u8().len());
     assert_eq!(
@@ -167,9 +168,6 @@ fn test_c_take() {
     check!(ffi::c_take_rust_vec_shared(shared_test_vec.clone()));
     check!(ffi::c_take_rust_vec_shared_index(shared_test_vec.clone()));
     check!(ffi::c_take_rust_vec_shared_push(shared_test_vec.clone()));
-    check!(ffi::c_take_rust_vec_shared_truncate(
-        shared_test_vec.clone()
-    ));
     check!(ffi::c_take_rust_vec_shared_clear(shared_test_vec.clone()));
     check!(ffi::c_take_rust_vec_shared_forward_iterator(
         shared_test_vec,