diff options
author | Christian Poveda Ruiz <31802960+pvdrz@users.noreply.github.com> | 2023-01-10 17:00:27 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-10 17:00:27 -0500 |
commit | e6dd2c636302401a54f72ec1f3c74323bb7e49ab (patch) | |
tree | d0e15a8904548eca9a6dbe19bff078a284991d55 | |
parent | 8ebeef4502e8661ea43dcc614d3a82b23c9dbaf5 (diff) |
Document semantic difference between constructors and wrappers (#2385)
-rw-r--r-- | book/src/cpp.md | 78 |
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. |