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

Interface/Protocol use in foreign generated code #2189

Closed
dowski opened this issue Jul 16, 2024 · 8 comments
Closed

Interface/Protocol use in foreign generated code #2189

dowski opened this issue Jul 16, 2024 · 8 comments

Comments

@dowski
Copy link

dowski commented Jul 16, 2024

Thanks for uniffi, I'm using v 0.28 and it's been working out well in my early explorations. I've hit a snag though.

If I create a UDL like:

namespace sample {};

interface Foo {
  Bar doBar();
};

interface Bar {
  void doNothing();
};

it seems like the concrete type is used in the generated Kotlin FooInterface code, something like:

public interface FooInterface {
  fun `doBar`() : Bar

Is there any way to tell uniffi to use BarInterface in place of Bar in the generated FooInterface?

@mhammond
Copy link
Member

It looks like #1352 tried to do exactly this. I'm not sure if that regressed or was never implemented.

@dowski
Copy link
Author

dowski commented Jul 16, 2024

Interesting, thanks! I can poke at it a bit more. In my real world case, I think it's using async methods so maybe there's some slight difference there (or like you said, there was a regression at some point).

@jmartinesp
Copy link
Contributor

jmartinesp commented Jul 26, 2024

It looks like #1352 tried to do exactly this. I'm not sure if that regressed or was never implemented.

Never implemented, what was implemented instead is that Bar is now open. My first implementation returned interfaces and it seemed to work, but it was way more complex than the final one and I think it could become a problem for some use case I can't remember now or find the comment that pointed it out. Maybe it was related to existing mocking strategies.

EDIT: it turns out I was thinking about this, which was initially part of the solution that was implemented, nothing actually related to this. So I think in the end it was just a matter of how complex the new code would be and how hard it would be to maintain it.

@dowski
Copy link
Author

dowski commented Aug 1, 2024

Thanks for the insight. I can work with the open class implementation. Feel free to close this if it's not feasible.

@mhammond
Copy link
Member

mhammond commented Oct 7, 2024

Feel free to close this if it's not feasible.

I had a bit of a look and it is feasible. It's a bit tricky because we can only do it for structs, not callbacks/traits, so it gets a little messy. It will never be feasible for args. I wonder how much of a win just these return types would be?

The open-class/NoPointer story seems pretty good and side-steps this - so does this actually make sense to do?

@jmartinesp
Copy link
Contributor

jmartinesp commented Oct 7, 2024

To be honest, I still have the feeling that it makes little sense to have both an interface that you can't return from any fake implementation of another of these interfaces and then an open class that can.

In my opinion, either we can use the interface with other interfaces, which is the most usual use case in our project (maybe not in others, but I don't really see how) or we should have just the open class because having both the interface an the open class is quite confusing. In fact, a colleague of mine tried to use the interfaces everywhere in our source code because it's 'the right/normal thing to do' in a java/kotlin context only to discover it wouldn't be possible to use them at all in the end because for a generated interface FooInterface another BarInterface would have a method like:

interface BarInterface {
    fun doSomething(): Foo // Can't cast FooInterface to Foo to be used here
}

As for passing the interfaces as parameters, I think my changes in the PR that was closed just made it fail on runtime, which may not be ideal but if you're passing some JVM implementation to Rust in most contexts you're doing something quite weird, and it's not really that different to the NoPointer implementation in that sense if I'm not mistaken, it's just less hidden.

@dowski
Copy link
Author

dowski commented Oct 7, 2024

Yeah, for clarity, having one or the other sounds good.

@mhammond
Copy link
Member

mhammond commented Oct 7, 2024

only to discover it wouldn't be possible to use them at all in the end because for a generated interface FooInterface another BarInterface would have a method like ...

Yeah exactly - this is what I meant by "never be viable for args" and also why this can't be used even for return values for callbacks etc. I saw failures at build time rather than build time, but if I tried to dynamically downcast I'm sure I could defer failure to runtime - but see no way of actually avoiding failure.

Thanks for both comments - I'll close this out for now, but that doesn't mean we can't revisit something like this in the future.

@mhammond mhammond closed this as completed Oct 7, 2024
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

3 participants