Skip to content

Commit e6dd2c6

Browse files
authored
Document semantic difference between constructors and wrappers (#2385)
1 parent 8ebeef4 commit e6dd2c6

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

book/src/cpp.md

+78
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,81 @@ cannot translate into Rust:
7979
large structs in C that would not fit into a register. This also applies to types with any base classes
8080
in the MSVC ABI (see [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#return-values)).
8181
Because bindgen does not know about these rules generated interfaces using such types are currently invalid.
82+
83+
## Constructor semantics
84+
85+
`bindgen` will generate a wrapper for any class constructor declared in the
86+
input headers. For example, this headers file
87+
88+
```c++
89+
class MyClass {
90+
public:
91+
MyClass();
92+
void method();
93+
};
94+
```
95+
96+
Will produce the following code:
97+
```rust,ignore
98+
#[repr(C)]
99+
#[derive(Debug, Copy, Clone)]
100+
pub struct MyClass {
101+
pub _address: u8,
102+
}
103+
extern "C" {
104+
#[link_name = "\u{1}_ZN7MyClass6methodEv"]
105+
pub fn MyClass_method(this: *mut MyClass);
106+
}
107+
extern "C" {
108+
#[link_name = "\u{1}_ZN7MyClassC1Ev"]
109+
pub fn MyClass_MyClass(this: *mut MyClass);
110+
}
111+
impl MyClass {
112+
#[inline]
113+
pub unsafe fn method(&mut self) {
114+
MyClass_method(self)
115+
}
116+
#[inline]
117+
pub unsafe fn new() -> Self {
118+
let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
119+
MyClass_MyClass(__bindgen_tmp.as_mut_ptr());
120+
__bindgen_tmp.assume_init()
121+
}
122+
}
123+
```
124+
This `MyClass::new` Rust method can be used as a substitute for the `MyClass`
125+
C++ constructor. However, the address of the value from inside the method will
126+
be different than from the outside. This is because the `__bindgen_tmp` value
127+
is moved when the `MyClass::new` method returns.
128+
129+
In contrast, the C++ constructor will not move the value, meaning that the
130+
address of the value will be the same inside and outside the constructor.
131+
If the original C++ relies on this semantic difference somehow, you should use the
132+
`MyClass_MyClass` binding directly instead of the `MyClass::new` method.
133+
134+
In other words, the Rust equivalent for the following C++ code
135+
136+
```c++
137+
MyClass instance = MyClass();
138+
instance.method();
139+
```
140+
141+
is not this
142+
143+
```rust,ignore
144+
let instance = MyClass::new();
145+
instance.method();
146+
```
147+
148+
but this
149+
150+
```rust,ignore
151+
let instance = std::mem::MaybeUninit::<MyClass>::uninit();
152+
MyClass_MyClass(instance.as_mut_ptr());
153+
instance.assume_init_mut().method();
154+
```
155+
156+
You can easily verify this fact if you provide a implementation for `MyClass`
157+
and `method` that prints the the `this` pointer address. However, you can
158+
ignore this fact if you know that the original C++ code does not rely on the
159+
instance address in its internal logic.

0 commit comments

Comments
 (0)