Skip to content

Commit

Permalink
Document semantic difference between constructors and wrappers (#2385)
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdrz authored Jan 10, 2023
1 parent 8ebeef4 commit e6dd2c6
Showing 1 changed file with 78 additions and 0 deletions.
78 changes: 78 additions & 0 deletions book/src/cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

0 comments on commit e6dd2c6

Please sign in to comment.