Repository: dtolnay/cxx (language: rust)

Issue:
rust::Fn call with non-const reference argument fails to compile
Repro:

```rust
// src/main.rs

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/header.h");

        fn call_back(i: &mut i32, f: fn(&mut i32));
    }
}

fn main() {
    let mut i = 0i32;
    ffi::call_back(&mut i, |_| {});
}
```

```cpp
// include/header.h

#pragma once
#include "rust/cxx.h"

inline void call_back(int32_t &i, rust::Fn<void(int32_t &)> f) {
  f(i);
}
```

The build failure is:

```console
In file included from example-f8c8c5261613a040/out/cxxbridge/crate/example/include/header.h:2,
                 from example-f8c8c5261613a040/out/cxxbridge/sources/example/src/main.rs.cc:1:
example-f8c8c5261613a040/out/cxxbridge/include/rust/cxx.h: In instantiation of ‘Ret rust::cxxbridge1::Fn<Ret(Args ...)>::operator()(Args ...) const [with Ret = void; Args = {int&}]’:
example-f8c8c5261613a040/out/cxxbridge/crate/example/include/header.h:5:6:   required from here
example-f8c8c5261613a040/out/cxxbridge/include/rust/cxx.h:478:29: error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘std::remove_reference<int&>::type’ {aka ‘int’}
  478 |   return (*this->trampoline)(std::move(args)..., this->fn);
      |          ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It's possible that `std::move` needs to be `std::forward` instead, or something similar.


Discussion / hints:
Note that the same thing with const reference arguments does work.

```diff
-         fn call_back(i: &mut i32, f: fn(&mut i32));
+         fn call_back(i: &i32, f: fn(&i32));

- inline void call_back(int32_t &i, rust::Fn<void(int32_t &)> f) {
+ inline void call_back(const int32_t &i, rust::Fn<void(const int32_t &)> f) {
```

Fix patch:
diff --git a/include/cxx.h b/include/cxx.h
index cdc63fbff..e8a206fc9 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -475,7 +475,7 @@ void panic [[noreturn]] (const char *msg);
 #define CXXBRIDGE1_RUST_FN
 template <typename Ret, typename... Args>
 Ret Fn<Ret(Args...)>::operator()(Args... args) const noexcept {
-  return (*this->trampoline)(std::move(args)..., this->fn);
+  return (*this->trampoline)(std::forward<Args>(args)..., this->fn);
 }
 
 template <typename Ret, typename... Args>


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index fcbe15309..4b37b20e0 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -157,6 +157,8 @@ pub mod ffi {
         fn c_take_ref_rust_vec_copy(v: &Vec<u8>);
         fn c_take_ref_shared_string(s: &SharedString) -> &SharedString;
         fn c_take_callback(callback: fn(String) -> usize);
+        fn c_take_callback_ref(callback: fn(&String));
+        fn c_take_callback_mut(callback: fn(&mut String));
         fn c_take_enum(e: Enum);
         fn c_take_ns_enum(e: AEnum);
         fn c_take_nested_ns_enum(e: ABEnum);
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index ba12ed45b..214bfcd6c 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -494,6 +494,16 @@ void c_take_callback(rust::Fn<size_t(rust::String)> callback) {
   callback("2020");
 }
 
+void c_take_callback_ref(rust::Fn<void(const rust::String &)> callback) {
+  const rust::String string = "2020";
+  callback(string);
+}
+
+void c_take_callback_mut(rust::Fn<void(rust::String &)> callback) {
+  rust::String string = "2020";
+  callback(string);
+}
+
 void c_take_enum(Enum e) {
   if (e == Enum::AVal) {
     cxx_test_suite_set_correct();
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 74acbf4fd..b624d3933 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -160,6 +160,8 @@ void c_take_ref_rust_vec_index(const rust::Vec<uint8_t> &v);
 void c_take_ref_rust_vec_copy(const rust::Vec<uint8_t> &v);
 const SharedString &c_take_ref_shared_string(const SharedString &s);
 void c_take_callback(rust::Fn<size_t(rust::String)> callback);
+void c_take_callback_ref(rust::Fn<void(const rust::String &)> callback);
+void c_take_callback_mut(rust::Fn<void(rust::String &)> callback);
 void c_take_enum(Enum e);
 void c_take_ns_enum(::A::AEnum e);
 void c_take_nested_ns_enum(::A::B::ABEnum e);
diff --git a/tests/test.rs b/tests/test.rs
index 32b8e8323..bb545bfa4 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -204,7 +204,22 @@ fn test_c_callback() {
         0
     }
 
+    #[allow(clippy::ptr_arg)]
+    fn callback_ref(s: &String) {
+        if s == "2020" {
+            cxx_test_suite_set_correct();
+        }
+    }
+
+    fn callback_mut(s: &mut String) {
+        if s == "2020" {
+            cxx_test_suite_set_correct();
+        }
+    }
+
     check!(ffi::c_take_callback(callback));
+    check!(ffi::c_take_callback_ref(callback_ref));
+    check!(ffi::c_take_callback_mut(callback_mut));
 }
 
 #[test]