-
Notifications
You must be signed in to change notification settings - Fork 233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support foreign implements of trait interfaces #1578
Comments
The main question I have about this is if we should give some support for using concrete types rather than always using type-erased versions. The main issue I see is when Rust passes a foreign object back to the foreign code. In this case, it might be nice for UniFFI to automagically convert it back to the concrete object, rather than passing back some sort of type-erased trait object. This eliminates a lot of overhead, since you can directly call methods on the object. With a trait object, you'd have to lower/lift the arguments across the FFI to pass them to the scaffolding function, then the scaffolding function would lower/lift them again to call into the callback interface. I think that case could be handled if UniFFI inserted a This sounds like more complexity than it's worth to me, but I wanted to mention for completeness. Another way to address this would be to create an actual vtable for the trait interface. Foreign objects would use a vtable would have functions on the foreign side. This avoids the round-tripping issue that I mentioned above, although I think you would still need to lower/lift the arguments once. |
This brings the concepts closer together and should make it easier to allow trait interfaces to be created from foreign objects (mozilla#1578). - Refactored `get_or_insert_object()` to just be `get_object()`. Ordered the metadata enum so that object definitions always come before methods. - Removed the `Result` return from `add_known_type()`, since it couldn't fail anyways.
This brings the concepts closer together and should make it easier to allow trait interfaces to be created from foreign objects (mozilla#1578). - Refactored `get_or_insert_object()` to just be `get_object()`. Ordered the metadata enum so that object definitions always come before methods. - Removed the `Result` return from `add_known_type()`, since it couldn't fail anyways.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there`s a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Refactored the callback interface code so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (mozilla#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Refactored the callback interface code so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (mozilla#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Factored out the callback interface impl and interface/protocol templates so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (mozilla#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Factored out the callback interface impl and interface/protocol templates so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (mozilla#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Factored out the callback interface impl and interface/protocol templates so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (mozilla#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
This is in preparation of mozilla#1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
This is in preparation of #1578 -- allowing foreign languages to implement traits. One issue there is that if we want to allow foreign languages to implement traits, then any Result return types need to implement `LiftReturn`, and that impl currently has a bound on `E: From<UnexpectedUniFFICallbackError`. IOW, it's only implemented for errors that can be converted from UnexpectedUniFFICallbackError. This makes sense when the trait is actually used by the foreign languages, however not all traits are intended for that use. For that matter, some libraries may be fine panicking in the the face of unexpected callback errors. By using the auto-ref specialization hack, we're able to convert the error if there's a `From<UnexpectedUniFFICallbackError>` impl, but use a panicking version if not. The trait bound is no longer required to implement `LiftReturn`.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Factored out the callback interface impl and interface/protocol templates so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (mozilla#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
Scaffolding: * Generate a struct that implements the trait using a callback interface callback * Make `try_lift` input a callback interface handle and create one of those structs. * Don't use `try_lift` in the trait interface method scaffolding. `try_lift` expects to lift a callback handle, but scaffolding methods are called with a leaked object pointer. * Removed the unused RustCallStatus param from the callback initialization function Kotlin/Python/Swift: * Factored out the callback interface impl and interface/protocol templates so it can also be used for trait interfaces. * Changed the callback interface handle map code so that it doesn't try to re-use the handles. If an object is lowered twice, we now generate two different handles. This is required for trait interfaces, and I think it's also would be the right thing for callback interfaces if they could be passed back into the foreign language from Rust. * Make `lower()` return a callback interface handle. * Added some code to clarify how we generate the protocol and the implementation of that protocol for an object Other: * Trait interfaces are still not supported on Ruby. * Updated the coverall bindings tests to test this. * Updated the traits example, although there's definitely more room for improvement. TODO: I think a better handle solution (#1730) could help with a few things: * We're currently wrapping the object every time it's passed across the FFI. If the foreign code receives a trait object, then passes it back to Rust. Rust now has a handle to the foreign impl and that foreign impl just calls back into Rust. This can lead to some extremely inefficent FFI calls if an object is passed around enough. * The way we're coercing between pointers, usize, and uint64 is probably wrong and at the very least extremely brittle. There should be better tests for reference counts, but I'm waiting until we address the handle issue to implement them.
We recently added support for trait interfaces (AKA sending Box from Rust to the foreign code). We should extend that code to also allow the foreign code to pass classes that implement the interface to APIs that expect a trait object. In the manual example, that means if they define a
ForeignButton
class that implementsButton
, they can pass that topress()
..This would create some nice symmetry and combine well with #1576.
It also addresses a real use-case we have in application-services. We have Rust sync engines for our components, but for historical reasons we also have sync engines defined on the foreign side and don't want to transition away right now. It would be really great if we could make it so
SyncManager
could seamlessly work with both types of engines at once.┆Issue is synchronized with this Jira Task
┆Issue Number: UNIFFI-277
The text was updated successfully, but these errors were encountered: