Rantanen / intercom

Object based cross-language FFI for Rust
MIT License
63 stars 7 forks source link

Replace tyhandler with Rust's type system? #108

Closed Rantanen closed 4 years ago

Rantanen commented 5 years ago

A while back, I started pondering implementing a TypeSystem trait:

trait TypeSystem {
    type StringType;
    type VariantType;
    type CollectionType;
}

struct AutomationTypeSystem;
impl TypeSystem for AutomationTypeSystem {
    type StringType = BString;
    type VariantType = Variant;
    type CollectionTYpe = SafeArray;
}

This would allow creating generic methods that handle different type systems, such as:

trait Foo<TS: TypeSystem> {
    fn bar(s: String) -> TS::StringType;
}

However I started wondering whether it would be possible to allow Intercom users to extend our type systems in such a way that they could introduce their own types that differ for each type system.

trait Representation<TS: TypeSystem> {
    type InExternType: IntercomInput<Self>;
    type OutExternType: IntercomOutput<Self>;
}

We could then hopefully provide a blanket default impl, which takes care of the types that do not differ between the type systems:

impl<T, TS:TypeSystem> Representation<TS> for T {
    type InExternType = Self;
    type OutExternType = Self;
}

And for types that do differ, we'd implement the type system specific types with an explicit trait impls:

trait Representation<AutomationTypeSystem> for String {
    type InExternType = raw::InBSTR;
    type OutExternType = raw::OutBSTR;
}

impl IntercomInput<String> for raw::InBSTR { ... }
impl IntercomOutput<String> for raw::OutBSTR { ... }

At this point our extern functions would take the following form (with error handling omitted):

pub extern "stdcall" fn MyStruct_MyInterface_my_method_automation(
    this_ptr: *mut RawComPtr ,
    string_arg: <String as Representation<AutomationTypeSystem>>::InExternType,
    number_arg: <i32 as Representation<AutomationTypeSystem>>::InExternType,
    out: *mut <String as Representation<AutomationTypeSystem>>::OutExternType,
) -> HRESULT
{
    let this = ComBox::<MyStruct>::from_ptr::<MyInterface, AutomationTypeSystem>::(this_ptr);
    let value = this.my_method(
        InputIntercomConversion::into_owned(string_arg).into_final(),
        InputIntercomConversion::into_owned(number_arg).into_final(),
    )

    *out = OutputIntercomConversion::into_final( value ):
}

The big downside here is that without our current tyhandlers, the proper COM type isn't resolved in the model. This means that the C++ and IDL generators may need to do some extra work to deduce the correct type for the type system (Currently they just do InBSTR -> BSTR, *const c_char -> char*. After this change they need to do String -> BSTR, String -> char* depending on the type system).

However supporting user types in those generators would need a change of some sort for them anyway.

The big benefits with this change are:

Rantanen commented 5 years ago

A quick draft of the mechanisms in playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=cacd413edc05c59e6c3b8e6dcc56d518

Rantanen commented 5 years ago

And I just realized that this provides one option for functions to react to type system changes.

#[com_interface]
trait IFoo {
    fn type_system_specific_function<TS: TypeSystem>();
}