Skip to content
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

Possibility of exposing a Trait as a vtable in current release? #147

Closed
tomleavy opened this issue Jan 25, 2023 · 12 comments
Closed

Possibility of exposing a Trait as a vtable in current release? #147

tomleavy opened this issue Jan 25, 2023 · 12 comments
Assignees

Comments

@tomleavy
Copy link

tomleavy commented Jan 25, 2023

I would like to take a Trait that I have and allow someone from the C side to provide an implementation of it in order to create a generic Rust struct internally. Based on the current documentation and my browsing of issues here I'm not sure I would accomplish that (even in a workaround type of way) and how that will improve with any changes that are coming.

Usage would be something like this

pub trait SomeTrait {
    fn some_method(&self, input: &[u8]) -> Result<Vec<u8>,  SomeError>;
    fn some_other_method(&mut self) -> String;
}

pub struct SomeStruct<T: SomeTrait> {
    inner_trait: SomeTrait
}

My assumption was that I would be able to do something like the following but I'm struggling with the specifics

#[ffi_export]
#[derive_ReprC]
pub struct SomeTraitVTable {
    some_method: extern "C" fn (self??, input: slice_ref<u8>,  out: Out???) -> c_int,
    some_other_method: extern "C" fn (&mut self??) -> repr_c::String
}

impl SomeTrait for SomeTraitVTable {
    // Not sure how this would work and how I would deal with &self and the output variable
}

#[ffi_export]
pub fn some_struct_new(repr_c::Box<SomeTraitVTable>) -> repr_c::Box<SomeStruct> {
    .....
}
@tomleavy
Copy link
Author

@danielhenrymantilla any thoughts?

@danielhenrymantilla
Copy link
Collaborator

danielhenrymantilla commented Jan 25, 2023

@tomleavy yes, this is definitely on safer-ffi's radar, and actually a feature that will be released rather soon:

If you are feeling adventurous, feel free to patch safer-ffi with that branch and see how it fares.

The idea would be:

  1. [dependencies.safer-ffi] # or [patch.crates-io.safer-ffi]
    git = "https://github.com/getditto/safer_ffi"
    # branch = "dyn-traits-part-2"
    rev = "17e003eddd4088289d3bd93ab3d28c4c79090be6"  # HEAD of `dyn-traits-part-2` as we speak
  2. #[derive_ReprC(dyn)]
    pub trait SomeTrait {
        fn some_method(&self, input: c_slice::Ref<'_, u8>) -> repr_c::Vec<u8>; // repr_c::Result does not exist yet
        fn some_other_method(&mut self) -> repr_c::String;
    }

You'll need the trait signatures to be made FFI-compatible by hand (matching safer-ffi's low-level current design, but the idea mid-term would be to have a kind of IntoFfi trait that would let us express that Vecs can be replaced by repr_c::Vecs, &[u8] with c_slice::Ref<'_, u8>, and so on); so you'll probably rather have to write this trait in an FFI-dedicated fashion:

#[derive_ReprC(dyn)]
pub trait FfiSomeTrait {
    fn ffi_some_method(&self, input: c_slice::Ref<'_, u8>) -> repr_c::Vec<u8>; // repr_c::Result does not exist yet
    fn ffi_some_other_method(&mut self) -> repr_c::String;
}

impl<T : SomeTrait> FfiSomeTrait for T {
    fn ffi_some_method(&self, input: c_slice::Ref<'_, u8>) -> repr_c::Vec<u8> {
        self.some_method(input.into()).into()
    }
    fn ffi_some_other_method(&mut self) -> repr_c::String {
        self.some_other_method().into()
    }
}

VirtualPtr

From there, the key wrapper type provided by safer-ffi which makes all the magic just click is VirtualPtr (in the prelude): a VirtualPtr<dyn 'lt + FfiSomeTrait> (feel free to add Send + Sync either as super-traits or inside that dyn trait) is then a ReprC type whose layout is that of:

#[repr(C)]
struct VirtualPtr<dyn 'lt /* + Send + Sync + */ + FfiSomeTrait> {
    ptr: *mut Erased,
    ffi_some_method: unsafe extern "C" fn(*const Erased, c_slice::Raw<u8>) -> repr_c::Vec_Layout<u8>,
    ffi_some_other_method: unsafe extern "C" fn(*mutErased) -> repr_c::String_Layout,
    ...
}
  • (another extension in the future would be to be able to customize the indirection of the vtable; for now an inlined vtable is just simpler across FFI).

And some convenience impls:

  • VirtualPtr<dyn ... + FfiSomeTrait> : ... + FfiSomeTrait 1
  • VirtualPtr<dyn ... + FfiSomeTrait> : From<Box<T>> where T : ... + FfiSomeTrait
    • (there are even From<&'lt mut T> impls for VirtualPtr<dyn 'lt + ...>)

You can see a VirtualPtr<dyn ...> as a Box<dyn ...>, except for the fact it even virtualizes the fact of being a Box: any pointer matching the requires API suits!

  • for instance, there is actually a

    impl<'lt, T : ... + FfiSomeTrait> From<&'lt mut T> for VirtualPtr<dyn 'lt + ... + FfiSomeTrait>

    since when within a 'lt lifetime, &'lt mut dyn is just as capable as Box<dyn 'lt +

    For more info, it turns out the language is currently considering adding, as a fully stand-alone abstraction, something that matches VirtualPtr conceptual design (but for the ffi considerations) quite closely: dyn *.

So, back to our example, we'd probably also want to write:

impl<'lt> SomeTrait for VirtualPtr<dyn 'lt + FfiSomeTrait> {
    fn some_method(&self, input: &[u8]) -> Vec<u8> {
        self.ffi_some_method(input.into()).into()
    }
    fn some_other_method(&mut self) -> String {
        self.ffi_some_other_method().into()
    }
}

See also, how the feature itself is being used to model Futures and Executor Handles (Spawn abstraction, technically) by safer-ffi itself: https://github.com/getditto/safer_ffi/blob/17e003eddd4088289d3bd93ab3d28c4c79090be6/src/dyn_traits/futures/executor.rs

Footnotes

  1. due to coherence reasons, this may be "duck-typed" rather than an official impl.

@danielhenrymantilla
Copy link
Collaborator

Back to a high-level view of the project, I should have this properly merged in master / released to crates.io within a month or two, hopefully with a quite lengthy book / guide about these things (my previous post may be a bit too densely packed with info, and lacking some examples)

@tomleavy
Copy link
Author

tomleavy commented Jan 25, 2023

This is awesome, thanks @danielhenrymantilla . Question about VirtualPtr, is that the type I would actually use when I want to utilize the trait someplace? My example calling new on SomeStruct that requires T: SomeTrait would be like so?

#[ffi_export]
pub fn some_struct_new(input: VirtualPtr<dyn '_ + FfiSomeTrait>) -> Box<SomeStruct> {
    Box::new(SomeStruct::new(input))
} 

@danielhenrymantilla
Copy link
Collaborator

Yeah, that's the idea 🙂

@stefunctional
Copy link
Contributor

Using the https://github.com/getditto/safer_ffi/tree/dyn-traits-part-2 branch, I defined the following trait but I can't seem to get the corresponding vtable definition in the generated C header:

pub trait ExampleTrait {
    fn method_ref(&self, s: &str, i: i32) -> String;
    fn method_mut(&mut self, s: &str, i: i32) -> String;
}

#[derive_ReprC(dyn)]
pub trait FfiExampleTrait {
    fn method_ref(&self, s: char_p::Ref<'_>, i: i32) -> repr_c::String;
    fn method_mut(&mut self, s: char_p::Ref<'_>, i: i32) -> repr_c::String;
}

I couldn't find any use of inventory for trait definitions in the proc-macro. Is generating vtables in the C header not implemented yet or am I missing something?

Thank you.

@danielhenrymantilla
Copy link
Collaborator

danielhenrymantilla commented Jan 31, 2023

@stefunctional this may be because the FFI type that makes use of said vtable is VirtualPtr<dyn FfiExampleTrait + ...>; and safer-ffi is currently "lazy" w.r.t. what makes it to headers:

  1. functions always make it to the header / are "ffi-exported";
  2. then anything transitively referred to by something ffi-exported (e.g., FFI types in the signature of a function) are then themselves ffi-exported.

So try adding:

// any of the two types involved should trigger the vtable definitions
#[ffi_export]
fn lets_see(
    _a: VirtualPtr<dyn 'static + Send + Sync + FfiExampleTrait>,
    _b: VirtualPtr<dyn '_ + FfiExampleTrait>,
)
{}

@stefunctional
Copy link
Contributor

That was it. Thank you very much for the quick answer!

@stefunctional
Copy link
Contributor

Do you need help to get the dyn-traits-part-2 branch to a point where it can be merged? It seems to be working well in the little experiment we conducted and we would be happy to help move this forward.

@danielhenrymantilla
Copy link
Collaborator

danielhenrymantilla commented Mar 16, 2023

Hey @stefunctional sorry for the late reply 🙇 I appreciate the offer, truly, and may take you up on it for later ideas 😄 but in this case the remaining effort was mostly:

and both of these tasks were thus about dealing with my messy code, so I was the best suited for them.

I have been quite busy with other stuff this past month, hence this being a bit more delayed than anticipated, but now all this has been released as 0.1.0-rc1 to crates.io

@danielhenrymantilla
Copy link
Collaborator

I'm gonna close this since #155, in a way, finished fixing it, but we can keep discussing about dyn traits here or over the Discussions 🙂

@stefunctional
Copy link
Contributor

No worries. Thank you very much for your work on this. I just skimmed through the change log and I am excited to try out this new version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants