Calling Rust from the Frontend
This document includes guides on how to communicate with your Rust code from your application frontend. To see how to communicate with your frontend from your Rust code, see Calling the Frontend from Rust.
Tauri provides a command primitive for reaching Rust functions with type safety, along with an event system that is more dynamic.
Commands
Tauri provides a simple yet powerful command
system for calling Rust functions from your web app.
Commands can accept arguments and return values. They can also return errors and be async
.
Basic Example
Commands can be defined in your src-tauri/src/lib.rs
file.
To create a command, just add a function and annotate it with #[tauri::command]
:
You will have to provide a list of your commands to the builder function like so:
Now, you can invoke the command from your JavaScript code:
Defining Commands in a Separate Module
If your application defines a lot of components or if they can be grouped,
you can define commands in a separate module instead of bloating the lib.rs
file.
As an example let’s define a command in the src-tauri/src/commands.rs
file:
In the lib.rs
file, define the module and provide the list of your commands accordingly;
Note the commands::
prefix in the command list, which denotes the full path to the command function.
The command name in this example is my_custom_command
so you can still call it by executing invoke("my_custom_command")
in your frontend, the commands::
prefix is ignored.
WASM
When using a Rust frontend to call invoke()
without arguments, you will need to adapt your frontend code as below.
The reason is that Rust doesn’t support optional arguments.
Passing Arguments
Your command handlers can take arguments:
Arguments should be passed as a JSON object with camelCase keys:
Arguments can be of any type, as long as they implement serde::Deserialize
.
The corresponding JavaScript:
Returning Data
Command handlers can return data as well:
The invoke
function returns a promise that resolves with the returned value:
Returned data can be of any type, as long as it implements serde::Serialize
.
Returning Array Buffers
Return values that implements serde::Serialize
are serialized to JSON when the response is sent to the frontend.
This can slow down your application if you try to return a large data such as a file or a download HTTP response.
To return array buffers in an optimized way, use tauri::ipc::Response
:
Error Handling
If your handler could fail and needs to be able to return an error, have the function return a Result
:
If the command returns an error, the promise will reject, otherwise, it resolves:
As mentioned above, everything returned from commands must implement serde::Serialize
, including errors.
This can be problematic if you’re working with error types from Rust’s std library or external crates as most error types do not implement it.
In simple scenarios you can use map_err
to convert these errors to String
s:
Since this is not very idiomatic you may want to create your own error type which implements serde::Serialize
.
In the following example, we use the thiserror
crate to help create the error type.
It allows you to turn enums into error types by deriving the thiserror::Error
trait.
You can consult its documentation for more details.
A custom error type has the advantage of making all possible errors explicit so readers can quickly identify what errors can happen.
This saves other people (and yourself) enormous amounts of time when reviewing and refactoring code later.
It also gives you full control over the way your error type gets serialized.
In the above example, we simply returned the error message as a string, but you could assign each error a code
so you could more easily map it to a similar looking TypeScript error enum for example:
In your frontend you now get a { kind: 'io' | 'utf8', message: string }
error object:
Async Commands
Asynchronous commands are preferred in Tauri to perform heavy work in a manner that doesn’t result in UI freezes or slowdowns.
If your command needs to run asynchronously, simply declare it as async
.
When working with borrowed types, you have to make additional changes. These are your two main options:
Option 1: Convert the type, such as &str
to a similar type that is not borrowed, such as String
.
This may not work for all types, for example State<'_, Data>
.
Example:
Option 2: Wrap the return type in a Result
. This one is a bit harder to implement, but works for all types.
Use the return type Result<a, b>
, replacing a
with the type you wish to return, or ()
if you wish to return null
, and replacing b
with an error type to return if something goes wrong, or ()
if you wish to have no optional error returned. For example:
Result<String, ()>
to return a String, and no error.Result<(), ()>
to returnnull
.Result<bool, Error>
to return a boolean or an error as shown in the Error Handling section above.
Example:
Invoking from JavaScript
Since invoking the command from JavaScript already returns a promise, it works just like any other command:
Channels
The Tauri channel is the recommended mechanism for streaming data such as streamed HTTP responses to the frontend. The following example reads a file and notifies the frontend of the progress in chunks of 4096 bytes:
See the channels documentation for more information.
Accessing the WebviewWindow in Commands
Commands can access the WebviewWindow
instance that invoked the message:
Accessing an AppHandle in Commands
Commands can access an AppHandle
instance:
Accessing Managed State
Tauri can manage state using the manage
function on tauri::Builder
.
The state can be accessed on a command using tauri::State
:
Accessing Raw Request
Tauri commands can also access the full tauri::ipc::Request
object which includes the raw body payload and the request headers.
In the frontend you can call invoke() sending a raw request body by providing an ArrayBuffer or Uint8Array on the payload argument, and include request headers in the third argument:
Creating Multiple Commands
The tauri::generate_handler!
macro takes an array of commands. To register
multiple commands, you cannot call invoke_handler multiple times. Only the last
call will be used. You must pass each command to a single call of
tauri::generate_handler!
.
Complete Example
Any or all of the above features can be combined:
Event System
The event system is a simpler communication mechanism between your frontend and the Rust. Unlike commands, events are not type safe, are always async, cannot return values and only supports JSON payloads.
Global Events
To trigger a global event you can use the event.emit or the WebviewWindow#emit functions:
Webview Event
To trigger an event to a listener registered by a specific webview you can use the event.emitTo or the WebviewWindow#emitTo functions:
Listening to Events
The @tauri-apps/api
NPM package offers APIs to listen to both global and webview-specific events.
-
Listening to global events
-
Listening to webview-specific events
The listen
function keeps the event listener registered for the entire lifetime of the application.
To stop listening on an event you can use the unlisten
function which is returned by the listen
function:
Additionally Tauri provides a utility function for listening to an event exactly once:
Listening to Events on Rust
Global and webview-specific events are also delivered to listeners registered in Rust.
-
Listening to global events
-
Listening to webview-specific events
The listen
function keeps the event listener registered for the entire lifetime of the application.
To stop listening on an event you can use the unlisten
function:
Additionally Tauri provides a utility function for listening to an event exactly once:
In this case the event listener is immediately unregistered after its first trigger.
To learn how to listen to events and emit events from your Rust code, see the Rust Event System documentation.
© 2024 Tauri Contributors. CC-BY / MIT