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

Swift Feature: "Main thread" attribute for object #2436

Open
lexoooooo opened this issue Feb 11, 2025 · 3 comments
Open

Swift Feature: "Main thread" attribute for object #2436

lexoooooo opened this issue Feb 11, 2025 · 3 comments

Comments

@lexoooooo
Copy link

lexoooooo commented Feb 11, 2025

The UniFFI user guide states:

Example: 

We insist that all object instances exposed to foreign-language code be Sync and Send, 

so that they’re safe to access regardless of the threading model of the calling code.

We do not allow thread-safety guarantees to be deferred to assumptions about how the code is called.

However, if an object is created on the main thread and all its methods are also called on the main thread, then even if the object does not satisfy Send, it is still thread-safe because it remains confined to a single thread.

Rough Design

Rust side

#[uniffi::main]
struct Text{
    ...
}

#[uniffi::export(main)]
impl Text{
    fn get_text(&self) -> String{
       ...
    }
}

Swift side

@Mainactor
class Text{
    // Make sure object only be used on main thread...
    func get_text() -> String{
        ...
    }

    deinit{// class may be deallocated on other thread...so we need call `Drop::drop` on main thread
        Task{@Mainactor in
             // call `Drop::drop` here...
        }
    }
}

Since Swift 5.5, we have the @MainActor attribute. UniFFI can leverage this by adding @MainActor to the generated code for such cases.

Why

This feature is particularly useful for UI handling. Most native UI frameworks require updates to be performed on the main thread, meaning our Rust code may always run on the main thread. Allowing a !Send object could reduce unnecessary synchronization overhead.

Reminder

An object may be deallocated (deinit) on a different thread. Therefore, on the foreign language side, we need to ensure that Rust’s Drop() runs on the main thread.

Problem

I am not familar with C# and Kotlin, so I don't know whether they have equivalent of @Mainactor

@mhammond
Copy link
Member

Rust has no concept of a "main thread", so I think something like this will need to be restricted to the bindings. In our current world, this implies we might need to nominate the special functions in uniffi.toml, then teach the Swift bindings to emit different code for those functions.

@mhammond mhammond changed the title Feature: "Main thread" attribute for object Swift Feature: "Main thread" attribute for object Feb 13, 2025
@lexoooooo
Copy link
Author

Rust has no concept of a "main thread", so I think something like this will need to be restricted to the bindings. In our current world, this implies we might need to nominate the special functions in uniffi.toml, then teach the Swift bindings to emit different code for those functions.

You are right, I realize that Rust does not have “main thread”. But it is possible to lose the restriction of “Send” bound in some way. The user can guarantee thread safety when all operations happen on one thread. Currently, it seems that we just wrap all objects with Arc instead of giving the user choices.

@mhammond
Copy link
Member

I believe we are unlikely to offer a capability where the Rust side of the world is only safe if the user promises the foreign binding doesn't do unsafe things, but where they are capable of accidentally doing unsafe things.

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

No branches or pull requests

2 participants