VassilisM
VassilisM

Reputation: 65

How can we make Rust struct instance methods available to Vue frontend using Tauri?

in short, Tauri application using Rust backend and Vue frontend. The #[tauri::command] is good and all for pure functions, but what happens if we need to preserve some kind of state in the backend, lets say that of a struct instance? This is my example:

Tauri entry point: main.rs

mod random_number_generator;
use random_number_generator::RandomNumberGenerator;

fn main() {
    // instance to manage the vector state
    let ng = RandomNumberGenerator::new();

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![generate]) <-- how to access the "generate" of ng?
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

A simple struct and its implementation: random_number_generator.rs.

use rand::Rng;

pub struct RandomNumberGenerator
{
    used_numbers_repository: Vec<i64>,
}

impl RandomNumberGenerator {
    pub fn new() -> Self {
        RandomNumberGenerator {
            used_numbers_repository: Vec::new()
        }
    }

    #[tauri::command]  <-- this is not allowed
    pub fn generate(&mut self, minimum: i64, maximum: i64) -> String {
        if minimum < 0 {
            return format!("Random number between {} and {}: minimum must be positive.", minimum, maximum);
        }

        if minimum >= maximum {
            return format!("Random number between {} and {}: maximum must be more than minimum.", minimum, maximum);
        }

        let mut rng = rand::thread_rng();
        let mut number: i64 = rng.gen_range(minimum..maximum + 1);

        while self.used_numbers_repository.contains(&number) {
            number = rng.gen_range(minimum..maximum + 1);
        }

        self.used_numbers_repository.push(number);
        format!("A random number between {} and {}? Here you go: {}.", minimum, maximum, number)
    }

    #[tauri::command]  <-- this is not allowed
    pub fn used_numbers(&self) -> String {
        self.used_numbers_repository.iter().map( |&id| id.to_string() + " ").collect()
    }
}

A simple vue component: RandomGenerator.vue

<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/tauri";

const generatedMessage = ref("");
const minimum = ref(0);
const maximum = ref(999);

async function generate() {
    generatedMessage.value = await invoke("generate", { minimum: minimum.value, maximum: maximum.value })
}
</script>

<template>
  <div class="card">
    <input type="number" v-model="minimum" placeholder="minimum" />
    <input type="number" v-model="maximum" placeholder="maximum" />
    <button type="button" @click="generate()">Generate</button>
  </div>

  <p>{{ generatedMessage }}</p>
</template>

#[tauri::command] is not allowed to struct methods.

Could that be possible without using some other packages, for state management for example?

Upvotes: 1

Views: 758

Answers (2)

VassilisM
VassilisM

Reputation: 65

After the accepted answer, the code in main.rs became like this:

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::State;
use std::sync::Mutex;
use number_generator::RandomNumberGenerator;
mod number_generator;

#[tauri::command]
fn generate(rng: State<Mutex<RandomNumberGenerator>>, minimum: i64, maximum: i64) -> String {
    rng.lock().unwrap().generate(minimum, maximum)
}

#[tauri::command]
fn numbers(rng: State<Mutex<RandomNumberGenerator>>) -> String {
    rng.lock().unwrap().used_numbers()
}


fn main() {
    let rng = Mutex::new(RandomNumberGenerator::new());

    tauri::Builder::default()
        .manage(rng)
        .invoke_handler(tauri::generate_handler![generate, numbers])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

And added in Generate.vue:

...
const generatedMessage = ref("");
const usedNumbers = ref("");
...
generatedMessage.value = await invoke("generate", { minimum: minimum.value, maximum: maximum.value })
usedNumbers.value = await invoke("numbers")
...
<h4>Used numbers</h4>
<p>{{ usedNumbers }}</p>

Upvotes: 1

Jonas Fassbender
Jonas Fassbender

Reputation: 2632

The #[tauri::command] is good and all for pure functions, but what happens if we need to preserve some kind of state in the backend, lets say that of a struct instance?

In Tauri you'd do this not with methods, but with State handled by your Tauri app with the Manager::manage method (similar to how endpoints in Rust web frameworks like Actix, Axum or Rocket work). Read more about state management in tauri in this section of the guide.

I.e. this could be a setup that works for Tauri using your RandomNumberGenerator as state while exposing your generate command to Vue.js:

use tauri::State;

pub struct RandomNumberGenerator
{
    used_numbers_repository: Vec<i64>,
}

#[tauri::command]
pub fn generate(rng: State<RandomNumberGenerator>, minimum: i64, maximum: i64) -> String {
    todo!();
}

fn main() {
    let ng = RandomNumberGenerator::new();

    tauri::Builder::default()
        .manage(ng) // manage ng as app state
        .invoke_handler(tauri::generate_handler![generate])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

In Vue you'd invoke this command like you do in your example:

await invoke("generate", { minimum: minimum.value, maximum: maximum.value })

Upvotes: 1

Related Questions