Tutorial de Tauri – Capítulo 6: Rust básico para desarrolladores web

Hasta ahora hemos trabajado solo con JavaScript y React para construir nuestra interfaz. Pero la verdadera potencia de Tauri está en el backend: funciones escritas en Rust que se ejecutan con rendimiento nativo.

En los próximos capítulos vas a escribir código Rust, así que antes necesitas conocer sus bases. No te preocupes: no vamos a convertirnos en expertos. Solo aprenderemos lo justo para entender el código que escribiremos. Y si vienes de JavaScript, verás que muchos conceptos te resultan familiares, solo cambia la sintaxis.

Variables y tipos

En Rust, las variables son inmutables por defecto:

// Inmutable - no puedes cambiar el valor
let nombre = "Eduardo";

// Mutable - puedes cambiarlo
let mut contador = 0;
contador = 1;

// Con tipo explícito
let edad: u32 = 30;
let precio: f64 = 19.99;
let activo: bool = true;

Comparación con JavaScript:

JavaScriptRust
const x = 5let x = 5
let x = 5let mut x = 5
numberi32, i64, f32, f64
stringString, &str
booleanbool

Tipos numéricos

A diferencia de JavaScript que solo tiene number, Rust distingue tipos:

// Enteros con signo (pueden ser negativos)
let a: i32 = -42;    // 32 bits
let b: i64 = -100;   // 64 bits

// Enteros sin signo (solo positivos)
let c: u32 = 42;
let d: u64 = 100;

// Decimales
let e: f64 = 3.14159;  // Recomendado

Para Tauri, generalmente usarás i32 o i64 para enteros y f64 para decimales.

Strings

Rust tiene dos tipos de strings:

// &str - literal, inmutable
let saludo: &str = "Hola";

// String - dinámico, puede crecer
let mut nombre = String::from("Eduardo");
nombre.push_str(" Revilla");

En los commands de Tauri, normalmente usarás String:

#[tauri::command]
fn saludar(nombre: String) -> String {
    format!("Hola, {}", nombre)
}

La macro format! es como los template literals de JavaScript.

Macros: por qué algunas funciones llevan !

En Rust verás funciones que terminan con un signo de exclamación: format!, println!, vec!. Se llaman macros.

¿Qué son? Piensa en ellas como funciones con superpoderes: pueden hacer cosas que las funciones normales no pueden, como aceptar un número variable de argumentos. El ! simplemente indica que es una macro en lugar de una función normal.

No necesitas entender cómo funcionan por dentro. Solo necesitas reconocerlas y saber usarlas. Estas son las tres que verás constantemente en Tauri:

// format! - crear un String formateado
let mensaje = format!("Hola, {}! Tienes {} años", nombre, edad);

// println! - imprimir en la consola de Rust (para depurar)
println!("El valor es: {}", variable);

// vec! - crear un vector (array)
let numeros = vec![1, 2, 3, 4, 5];

Si vienes de JavaScript, format! es el equivalente directo de los template literals:

// JavaScript
const mensaje = `Hola, ${nombre}! Tienes ${edad} años`;

// Rust
let mensaje = format!("Hola, {}! Tienes {} años", nombre, edad);

Los {} son los huecos donde se insertan los valores, en el orden en que los pasas después de la coma.

Vectores

El equivalente a los arrays de JavaScript:

// Crear vector
let frutas = vec!["manzana", "pera", "naranja"];

// Vector mutable
let mut numeros: Vec<i32> = Vec::new();
numeros.push(1);
numeros.push(2);

// Acceder
let primero = &numeros[0];

// Iterar
for num in &numeros {
    println!("{}", num);
}

Estructuras

Como los objetos de JavaScript, pero con tipos:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Usuario {
    nombre: String,
    edad: u32,
    email: String,
}

// Crear instancia
let usuario = Usuario {
    nombre: String::from("Eduardo"),
    edad: 30,
    email: String::from("edu@email.com"),
};

// Acceder
println!("Nombre: {}", usuario.nombre);

Los atributos #[derive(Serialize, Deserialize)] permiten convertir a/desde JSON. Imprescindible para commands.

Option: valores que pueden no existir

En JavaScript usas null. Rust usa Option:

let nombre: Option<String> = Some(String::from("Eduardo"));
let apellido: Option<String> = None;

// Usar el valor
match nombre {
    Some(n) => println!("Nombre: {}", n),
    None => println!("Sin nombre"),
}

// Forma abreviada
if let Some(n) = nombre {
    println!("Nombre: {}", n);
}

// Con valor por defecto
let n = apellido.unwrap_or(String::from("Desconocido"));

Result: manejo de errores

En lugar de excepciones, Rust usa Result:

fn dividir(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("No se puede dividir por cero"))
    } else {
        Ok(a / b)
    }
}

// Usar el resultado
match dividir(10.0, 2.0) {
    Ok(resultado) => println!("Resultado: {}", resultado),
    Err(error) => println!("Error: {}", error),
}

// Con el operador ?
fn calcular() -> Result<f64, String> {
    let resultado = dividir(10.0, 2.0)?;  // Propaga el error si falla
    Ok(resultado * 2.0)
}

Match

Es como switch pero más potente:

let numero = 3;

let texto = match numero {
    1 => "uno",
    2 => "dos",
    3 => "tres",
    4 | 5 => "cuatro o cinco",
    6..=10 => "entre seis y diez",
    _ => "otro",  // default
};

Funciones

// Función básica
fn saludar() {
    println!("Hola");
}

// Con parámetros y retorno
fn sumar(a: i32, b: i32) -> i32 {
    a + b  // Sin punto y coma = retorno
}

// Async
async fn obtener_datos() -> String {
    String::from("datos")
}

Atributos: instrucciones especiales

Verás código como #[tauri::command] o #[derive(Serialize)] encima de funciones y structs. Se llaman atributos y son instrucciones que le das al compilador.

Si has usado decoradores en Python (@app.route) o anotaciones en Java (@Override), el concepto es el mismo: modifican o añaden comportamiento al código que viene debajo.

Los que usarás en Tauri:

// Le dice a Tauri: "esta función es un command,
// hazla accesible desde JavaScript"
#[tauri::command]
fn mi_funcion() -> String {
    // ...
}

// Le dice a Rust: "genera automáticamente el código
// para convertir este struct a/desde JSON"
#[derive(Serialize, Deserialize)]
struct MiEstructura {
    nombre: String,
    edad: u32,
}

No necesitas entender todos los detalles. Solo recuerda: los atributos #[...] configuran cómo se comporta el código que tienen debajo.

Closures

Similar a las arrow functions:

// JavaScript: (a, b) => a + b
// Rust:
let sumar = |a, b| a + b;

// Uso con iteradores
let numeros = vec![1, 2, 3];
let dobles: Vec<i32> = numeros.iter().map(|x| x * 2).collect();

Resumen

Conceptos esenciales de Rust para Tauri:

  • Variables inmutables por defecto (let vs let mut)
  • Tipos específicos (i32, f64, String)
  • Vectores (Vec<T>)
  • Estructuras con Serde
  • Option para valores opcionales
  • Result para errores
  • Macros (format!, println!, vec!)
  • Atributos (#[...]) para configurar el comportamiento
  • Con esto tienes suficiente Rust para entender todo lo que viene. Ahora sí, vamos a lo emocionante: conectar tu frontend JavaScript con tu backend Rust.

    Nos vemos en el Capítulo 7.


    ¿Te está gustando este tutorial?

    Este tutorial forma parte del libro Tauri 2.0: Aplicaciones de Escritorio con React y Rust, disponible en Amazon en formato papel y ebook. El libro incluye todos los capítulos, tres proyectos completos paso a paso y contenido exclusivo que no encontrarás en el blog.

    Un saludo, y si aún no lo has hecho no olvides suscribirte a mi blog para no perderte los próximos posts  :-),

    También puedes seguirme en Twitter en ‎@revi_apps y no olvides que me ayudas mucho si compartes este post en las redes sociales.

    Deja un comentario

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

    Scroll al inicio