Passer au contenu principal

Appeler Rust depuis le frontend

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.

Exemple basique

Commands are defined in your src-tauri/src/main.rs file. To create a command, just add a function and annotate it with #[tauri::command]:

#[tauri::command]
fn my_custom_command() {
println!("J'ai été invoqué à partir de JS !");
}

You will have to provide a list of your commands to the builder function like so:

// Aussi dans main.rs
fn main() {
tauri::Builder::default()
// C'est ici que vous passez vos commandes
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("failed to run app");
}

Now, you can invoke the command from your JS code:

// Lors de l'utilisation du package npm de l'API Tauri :
import { invoke } from '@tauri-apps/api/tauri'
// Lors de l'utilisation du script global Tauri (si vous n'utilisez pas le package npm)
// Assurez-vous de définir `build.withGlobalTauri` dans `tauri.conf.json` sur true
const invoke = window.__TAURI__.invoke

// Invoque la commande
invoke('my_custom_command')

Passer des arguments

Your command handlers can take arguments:

#[tauri::command]
fn my_custom_command(invoke_message: String) {
println!("J'ai été invoqué à partir de JS, avec ce message: {}", invoke_message);
}

Arguments should be passed as a JSON object with camelCase keys:

invoke('my_custom_command', { invokeMessage: 'Salut !' })

Arguments can be of any type, as long as they implement serde::Deserialize.

Retourner des données

Command handlers can return data as well:

#[tauri::command]
fn my_custom_command() -> String {
"Hello from Rust!".into()
}

The invoke function returns a promise that resolves with the returned value:

invoke('my_custom_command').then((message) => console.log(message))

Returned data can be of any type, as long as it implements serde::Serialize.

Gestion des erreurs

If your handler could fail and needs to be able to return an error, have the function return a Result:

#[tauri::command]
fn my_custom_command() -> Result<String, String> {
// If something fails
Err("This failed!".into())
// If it worked
Ok("This worked!".into())
}

If the command returns an error, the promise will reject, otherwise, it resolves:

invoke('my_custom_command')
.then((message) => console.log(message))
.catch((error) => console.error(error))

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 Strings:

#[tauri::command]
fn my_custom_command() -> Result<(), String> {
// This will return an error
std::fs::File::open("path/that/does/not/exist").map_err(|err| err.to_string())?;
// Return nothing on success
Ok(())
}

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.

// Crée le type d'erreur qui représente toutes les erreurs possibles dans notre programme
#[derive(Debug, thiserror::Error)]
enum Error {
#[error(transparent)]
Io(#[from] std::io::Error)
}

// Nous devons implémenter manuellement serde::Serialize
impl serde::Serialize for Error {
fn serialize<S>(&self, serializer : S) -> Résultat<S::Ok, S::Error>

S: serde::ser::Serializer,
{
sérialiseur. erialize_str(self.to_string(). s_ref())
}
}

#[tauri::command]
fn my_custom_command() -> Result<(), Erreur> {
// Cela renverra une erreur
std::fs::File::open("path/that/does/not/exist")?
// Ne rien retourner en cas de succès
Ok(())
}

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 similar to C, this way you could more easily map it to a similar looking TypeScript error enum for example.

Commandes asynchrones

note

Async commands are executed on a separate thread using async_runtime::spawn. Commands without the async keyword are executed on the main thread unless defined with #[tauri::command(async)].

If your command needs to run asynchronously, simply declare it as async:

#[tauri::command]
async fn my_custom_command() {
// Appelez une autre fonction asynchrone et attendez qu'elle se termine
let result = some_async_function().await;
println!("Result: {}", result);
}

Since invoking the command from JS already returns a promise, it works just like any other command:

invoke('my_custom_command').then(() => console.log('Completed!'))

Accéder à la fenêtre dans les commandes

Commands can access the Window instance that invoked the message:

#[tauri::command]
async fn my_custom_command(window: tauri::Window) {
println!("Fenêtre: {}", window.label());
}

Accéder à un AppHandle dans les commandes

Commands can access an AppHandle instance:

#[tauri::command]
async fn my_custom_command(app_handle: tauri::AppHandle) {
let app_dir = app_handle.path_resolver().app_dir();
use tauri::GlobalShortcutManager;
app_handle.global_shortcut_manager().register("CTRL + U", move || {});
}

Accéder à l'état géré

Tauri can manage state using the manage function on tauri::Builder. The state can be accessed on a command using tauri::State:

struct MyState(String);

#[tauri::command]
fn my_custom_command(state: tauri::State<MyState>) {
assert_eq!(state.0 == "some state value", true);
}

fn main() {
tauri::Builder::default()
.manage(MyState("some state value".into()))
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

Création de plusieurs commandes

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!.

#[tauri::command]
fn cmd_a() -> String {
"Command a"
}
#[tauri::command]
fn cmd_b() -> String {
"Command b"
}

fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![cmd_a, cmd_b])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

Complete Example

Tout ou partie des fonctionnalités ci-dessus peuvent être combinées :


struct Database;

#[derive(serde::Serialize)]
struct CustomResponse {
message: String,
other_val: usize,
}

async fn some_other_function() -> Option<String> {
Some("response".into())
}

#[tauri::command]
async fn my_custom_command(
window: tauri::Window,
number: usize,
database: tauri::State<'_, Database>,
) -> Result<CustomResponse, String> {
println!("Called from {}", window.label());
let result: Option<String> = some_other_function().await;
if let Some(message) = result {
Ok(CustomResponse {
message,
other_val: 42 + number,
})
} else {
Err("No result".into())
}
}

fn main() {
tauri::Builder::default()
.manage(Database {})
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// Invocation depuis JS
invoke('my_custom_command', {
number: 42,
})
.then((res) =>
console.log(`Message: ${res.message}, Other Val: ${res.other_val}`)
)
.catch((e) => console.error(e))