This will be needed in order to properly support Control Flow Integrity. I reached out to @Darksonn to discuss CFI use in the Rust parts of the Linux kernel especially, and she gave me some really useful information for CFI implementation details that I'm pasting here verbatim (with her approval):
A few implementation tips:
In some scenarios you will need to create a "trampoline" to give a
function a different signature than its "native" signature. For
example, given:
trait Foo {
fn foo(&self);
}
impl Foo for String {
fn foo(&self) { println!("{}", self); }
}
Then String::foo will give you an fn(&String), and the hash stored for
this function pointer needs to be the hash for that signature. This
means that it cannot be the same function pointer as the one stored in
the Foo vtable for String, as that function pointer is invoked with
dynamic dispatch by callers that expect a signature of "pointer to
Foo" rather than "pointer to String".
Similarly, a Rust vtable also contains a function pointer to the
destructor, and the same problem applies here. But this one has one
difference as the same destructor can be in many vtables for different
traits.
Rust approached this by letting the "native" signature of
String::foo() be a pointer to "Foo", and if you create an fn(&String)
to the same function, then rustc generates a trammpoline function that
has the other signature and calls the original function. This ensures
that trampolines are not needed for the common case (the fp in the
vtable). The trampoline is just a function that will immediately jump
to the true implementation.
For destructors the reverse convention is chosen. This means that
trait vtables contain a trampoline to the destructor.
This will be needed in order to properly support Control Flow Integrity. I reached out to @Darksonn to discuss CFI use in the Rust parts of the Linux kernel especially, and she gave me some really useful information for CFI implementation details that I'm pasting here verbatim (with her approval):