summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poveda Ruiz <31802960+pvdrz@users.noreply.github.com>2023-01-10 17:00:27 -0500
committerGitHub <noreply@github.com>2023-01-10 17:00:27 -0500
commite6dd2c636302401a54f72ec1f3c74323bb7e49ab (patch)
treed0e15a8904548eca9a6dbe19bff078a284991d55
parent8ebeef4502e8661ea43dcc614d3a82b23c9dbaf5 (diff)
Document semantic difference between constructors and wrappers (#2385)
-rw-r--r--book/src/cpp.md78
1 files changed, 78 insertions, 0 deletions
diff --git a/book/src/cpp.md b/book/src/cpp.md
index 69285846..f5091b35 100644
--- a/book/src/cpp.md
+++ b/book/src/cpp.md
@@ -79,3 +79,81 @@ cannot translate into Rust:
large structs in C that would not fit into a register. This also applies to types with any base classes
in the MSVC ABI (see [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#return-values)).
Because bindgen does not know about these rules generated interfaces using such types are currently invalid.
+
+## Constructor semantics
+
+`bindgen` will generate a wrapper for any class constructor declared in the
+input headers. For example, this headers file
+
+```c++
+class MyClass {
+ public:
+ MyClass();
+ void method();
+};
+```
+
+Will produce the following code:
+```rust,ignore
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct MyClass {
+ pub _address: u8,
+}
+extern "C" {
+ #[link_name = "\u{1}_ZN7MyClass6methodEv"]
+ pub fn MyClass_method(this: *mut MyClass);
+}
+extern "C" {
+ #[link_name = "\u{1}_ZN7MyClassC1Ev"]
+ pub fn MyClass_MyClass(this: *mut MyClass);
+}
+impl MyClass {
+ #[inline]
+ pub unsafe fn method(&mut self) {
+ MyClass_method(self)
+ }
+ #[inline]
+ pub unsafe fn new() -> Self {
+ let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
+ MyClass_MyClass(__bindgen_tmp.as_mut_ptr());
+ __bindgen_tmp.assume_init()
+ }
+}
+```
+This `MyClass::new` Rust method can be used as a substitute for the `MyClass`
+C++ constructor. However, the address of the value from inside the method will
+be different than from the outside. This is because the `__bindgen_tmp` value
+is moved when the `MyClass::new` method returns.
+
+In contrast, the C++ constructor will not move the value, meaning that the
+address of the value will be the same inside and outside the constructor.
+If the original C++ relies on this semantic difference somehow, you should use the
+`MyClass_MyClass` binding directly instead of the `MyClass::new` method.
+
+In other words, the Rust equivalent for the following C++ code
+
+```c++
+MyClass instance = MyClass();
+instance.method();
+```
+
+is not this
+
+```rust,ignore
+let instance = MyClass::new();
+instance.method();
+```
+
+but this
+
+```rust,ignore
+let instance = std::mem::MaybeUninit::<MyClass>::uninit();
+MyClass_MyClass(instance.as_mut_ptr());
+instance.assume_init_mut().method();
+```
+
+You can easily verify this fact if you provide a implementation for `MyClass`
+and `method` that prints the the `this` pointer address. However, you can
+ignore this fact if you know that the original C++ code does not rely on the
+instance address in its internal logic.