The current setup for converting Rust types to-and-from an FFI-safe representation uses a pair of traits FromAbi and IntoAbi. It would simplify various parts of the code generation logic if we could unify these traits into a single Abi trait. However, there currently a couple of places where a type handles being sent to C# vs when it's received from C#. These are discussed in the sections below.
I'm slightly hesitant to unify these two fully, as I'm not yet 100% certain that all Rust types that we want to be able to marshal to C# can be uniformly represented when sending vs receiving. However, there are pretty big advantages to unifying them, so I think it's worth the potential risk of running into edge cases that don't work in the future.
String
Right now, String is converted into a RawVec<u8> when passed to C#, but is received as a RawSlice<u16> when received from C#. This difference is there for two reason:
The C# side can't actually create a RawVec directly, since it can't allocate memory with Rust's allocator.
Passing the raw utf-16 slice to Rust and letting rust handle the utf-8 conversion avoids an unnecessary string copy on the C# side.
To fix these, can can export a helper function that takes a RawSlice<u16> and returns a RawVec<u8>. When passing a string to Rust code, the C# code can call the helper function to convert its string to a Rust-compatible representation while still avoiding the extra string copy.
Zero-Sized Types
Zero-sized types aren't FFI safe, and therefore don't generally implement FromAbi or IntoAbi. However, we currently make an exception for (), which implements IntoAbi. This is to uniformly handle exported Rust functions that don't return a value. However, this approach isn't generalized to all ZSTs, and is potentially unsound if () is used as a field in a struct that is returned from a Rust function.
To make it generally possible to pass ZSTs to-and-from C#, we can instead represent the types as a single byte in FFI. While this is slightly less efficient, it ensures that the generated code will always be FFI-safe. Exported functions that don't return a value should still generate a void function in C# and the raw bindings, however functions that explicitly return () will likely have to be generated to return a dummy single-byte type.
The current setup for converting Rust types to-and-from an FFI-safe representation uses a pair of traits
FromAbi
andIntoAbi
. It would simplify various parts of the code generation logic if we could unify these traits into a singleAbi
trait. However, there currently a couple of places where a type handles being sent to C# vs when it's received from C#. These are discussed in the sections below.I'm slightly hesitant to unify these two fully, as I'm not yet 100% certain that all Rust types that we want to be able to marshal to C# can be uniformly represented when sending vs receiving. However, there are pretty big advantages to unifying them, so I think it's worth the potential risk of running into edge cases that don't work in the future.
String
Right now,
String
is converted into aRawVec<u8>
when passed to C#, but is received as aRawSlice<u16>
when received from C#. This difference is there for two reason:RawVec
directly, since it can't allocate memory with Rust's allocator.To fix these, can can export a helper function that takes a
RawSlice<u16>
and returns aRawVec<u8>
. When passing astring
to Rust code, the C# code can call the helper function to convert itsstring
to a Rust-compatible representation while still avoiding the extra string copy.Zero-Sized Types
Zero-sized types aren't FFI safe, and therefore don't generally implement
FromAbi
orIntoAbi
. However, we currently make an exception for()
, which implementsIntoAbi
. This is to uniformly handle exported Rust functions that don't return a value. However, this approach isn't generalized to all ZSTs, and is potentially unsound if()
is used as a field in a struct that is returned from a Rust function.To make it generally possible to pass ZSTs to-and-from C#, we can instead represent the types as a single byte in FFI. While this is slightly less efficient, it ensures that the generated code will always be FFI-safe. Exported functions that don't return a value should still generate a
void
function in C# and the raw bindings, however functions that explicitly return()
will likely have to be generated to return a dummysingle-byte
type.