Created
August 4, 2021 14:41
-
-
Save bendk/7a46ffb6cea0f41947bebe663a8ccbab to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This code comes from taking James' of components and composition and trying to run the other way | |
# with it. Instead of (maybe in addition to) creating composible components in uniffi_bindgen, what if we | |
# tried to write them in the target language? | |
# | |
# The basic idea would be: | |
# - Define a ViaFFI interface | |
# - Map Type enums to objects that implement the interface for that type. How this works will | |
# depend on the target language: | |
# - For python, we could setup a dict that maps the canonical type name to an object | |
# - For Rust, we could map the Type enum to a struct that implements the ViaFFi trait (I'm | |
# working on that in my current PR) | |
# - Since we have a well-defined interface and a way to map types to implementations of that | |
# interface, then it's easy to generate lower/lift/read/write expressions for any type. | |
# | |
# It seems like a nice and simple system to me and I think it would help with a few of our current | |
# issues: | |
# - Importing types from other UDLs | |
# - Custom types that adapt other ViaFFI implementations | |
# Here's some dreamcode for how the bindings might be generated in python | |
# | |
# Todo: | |
# - Improve the way ViaFFI impls are referenced |t seems very easy with this code to try to use a ViaFFIMap value | |
# before it's been generated. | |
# - Add system for types to specify modules to import (Like othercrate and json below) | |
class ViaFFI: | |
def lift(self, val): pass | |
def read(self, buf_stream): pass | |
def lower(self, val): pass | |
def write(self, val, buf_builder): pass | |
# Map canonnical_type_name to objects that implement ViaFFI | |
ViaFFIMap = { } | |
# Primitives are easy to define | |
class ViaFFIPrimitive(ViaFFI): | |
def __init__(self, size, format): | |
self.size = size | |
self.format | |
def read(self, buf_stream): | |
return buf_stream.unpack_from(self.size, self.format) | |
... | |
ViaFFIMap["u8"] = ViaFFIPrimitive(1, ">b") | |
# Optional can be defined using composition | |
class ViaFFIOptional(ViaFFI): | |
def __init__(self, inner) | |
self.inner = inner | |
def read(self, buf_stream): | |
flag = buf.unpack_from(1, ">b") | |
if flag == 0: | |
return None | |
elif flag == 1: | |
return inner.read(buf_stream) | |
else: | |
raise InternalError("Unexpected flag byte for Optional") | |
... more methods | |
ViaFFIMap["OptionalString"] = ViaFFIOptional(ViaFFIMap["String"]) | |
# Record could also be defined using composition. | |
ViaFFIMap["RecordFoo"] = ViaFFIRecord([ | |
('field1', ViaFFIMap['String']), | |
('field2', ViaFFIMap['i32']), | |
('field3', ViaFFIMap['OptionalString']), | |
]) | |
# You could reference types from other crates | |
ViaFFIMap["ImportedType"] = othercrate.ViaFFIMap["Type"] | |
ViaFFIMap["OptionalImportedType"] = Optional(ViaFFIMap["ImportedType"]) | |
ViaFFIMap["RecordWithImportedType"] ViaFFIRecord([ | |
('field1', ViaFFIMap['String']), | |
('field2', ViaFFIMap['ImportedType']), | |
]) | |
# You could implement custom types. Imagine this code coming from a custom | |
# template -- maybe from a separate extension crate. | |
class ViaFFIJsonValue(ViaFFI): | |
def read(self, buf_stream): | |
return json.loads(ViaFFIMap["String"].read(buf_stream)) | |
ViaFFIMap["CustomJsonValue"] = ViaFFIJsonValue() | |
# CustomJsonValue could now be nested in an Option, Record, whatever. | |
# Functions would look something like this: | |
def call_constructor(arg1, arg2): | |
ViaFFIMap["MyInterface"].lift( | |
_UniFFILib.func_name( | |
ViaFFIMap["String"].lower(arg1), | |
ViaFFIMap["MyRecord"].lower(arg2) | |
)) |
* Runtime overhead in the generated code
I've been wondering about this one too. There are a couple other methods I've been wondering about:
- You could index each type. Then instead of
ViaFfiMap
you haveViaFfiArray
. I think this might be zero cost, since the lookups are going to be in the form ofstatic_array[const_index]
- You could create a set of classes that only have static methods. This is close to how Rust does it, but it kind of seems weird to define a class for each
Type
instance -- including types that wrap other types like Option. - I think each binding might want to handle this differently. Each language has different features that it's going to want to use.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting! There's a lot to like here, I wanted to also highlight a few potential tradeoffs that we'll need to be deliberate about: