2 - Tipos de datos y funciones - los bloques de construcción en Rust

2 - Tipos de datos y funciones - los bloques de construcción en Rust

¡Bienvenidos de nuevo a Rustaceo! Continuando con nuestra serie sobre Rust y Pokémon, hoy exploraremos los tipos de datos y las funciones en Rust. Estos son los bloques de construcción fundamentales que te permitirán crear programas más complejos y efectivos. Acompáñame mientras descubrimos cómo utilizar estos conceptos con ejemplos del mundo Pokémon.

Tipos de datos en Rust #

Rust es un lenguaje de tipo estático, lo que significa que el tipo de cada variable debe ser conocido en tiempo de compilación. Esto ayuda a prevenir errores y garantiza la seguridad del código.

Tipos escalares #

Los tipos escalares representan un solo valor. En Rust, tenemos cuatro tipos escalares principales:

  1. Enteros (i8, u8, i16, u16, etc.): Números sin punto decimal.
  2. Puntos Flotantes (f32, f64): Números con punto decimal.
  3. Booleanos (bool): Valores verdaderos o falsos.
  4. Caracteres (char): Representa un solo carácter Unicode.

Ejemplo:

let nivel_item1: u8 = 25;        // Entero sin signo
let peso_snorlax: f32 = 460.0;     // Punto flotante de 32 bits
let es_legendario: bool = false;   // Valor booleano
let inicial: char = 'P';           // Carácter

Tipos compuestos #

Los tipos compuestos pueden agrupar múltiples valores en un solo tipo. Los dos tipos compuestos principales en Rust son:

  1. Tuplas (tuple): Agrupan valores de diferentes tipos.
  2. Arreglos (array): Colección de valores del mismo tipo y tamaño fijo.

Ejemplo de Tupla:

let item1: (&str, u8, f32) = ("Item1", 25, 6.0);
// (Nombre, Nivel, Peso)

Ejemplo de Arreglo:

let equipo: [&str; 3] = ["Item4", "Item2", "Item3"];

Usando estructuras para definir tipos personalizados #

Las estructuras (structs) te permiten crear tipos de datos más complejos.

Ejemplo: Definir una Estructura para un Pokémon

struct Pokémon {
    nombre: String,
    tipo: String,
    nivel: u8,
    salud: u16,
}

let item4 = Pokémon {
    nombre: String::from("Item4"),
    tipo: String::from("Planta/Veneno"),
    nivel: 5,
    salud: 45,
};

println!("{} es de tipo {} y está en el nivel {}.", item4.nombre, item4.tipo, item4.nivel);

Enums: tipos personalizados con variantes #

Los enums te permiten definir un tipo que puede ser uno de varios variantes.

Ejemplo: Estados de un Pokémon

enum Estado {
    Saludable,
    Envenenado,
    Paralizado,
    Dormido,
    Confundido,
}

let estado_actual = Estado::Envenenado;

match estado_actual {
    Estado::Saludable => println!("El Pokémon está en perfectas condiciones."),
    Estado::Envenenado => println!("El Pokémon está envenenado."),
    _ => println!("El Pokémon tiene un estado especial."),
}

Funciones en Rust #

Las funciones son bloques de código reutilizables que realizan tareas específicas.

Sintaxis Básica:

fn nombre_funcion(parametros) -> TipoDeRetorno {
    // Cuerpo de la función
}

Ejemplo: función para calcular el daño de un ataque #

fn calcular_dano(ataque: u16, defensa: u16, poder: u16) -> u16 {
    let dano = ((ataque as f32 / defensa as f32) * poder as f32 / 50.0 + 2.0) as u16;
    dano
}

let dano_causado = calcular_dano(55, 40, 60);
println!("El daño causado es {} puntos.", dano_causado);

En este ejemplo:

  • La función calcular_dano toma tres parámetros: ataque, defensa y poder.
  • Calcula el daño basado en una fórmula simplificada.
  • Retorna el daño como un u16.

Funciones con parámetros y retornos #

Las funciones pueden tener múltiples parámetros y pueden retornar valores.

Ejemplo: Función para Atrapar un Pokémon

fn atrapar_elemento(pokeball: &str, probabilidad: f32) -> bool {
    if probabilidad >= 0.5 {
        println!("¡Has atrapado al Pokémon usando una {}!", pokeball);
        true
    } else {
        println!("El Pokémon escapó de la {}.", pokeball);
        false
    }
}

let exito = atrapar_elemento("Poké Ball", 0.7);

Closures: funciones anónimas #

Los closures son funciones anónimas que pueden capturar variables del entorno.

Ejemplo: Ordenar una Lista de Pokémon por Nivel

let mut elemento_equipo = vec![
    ("Item1", 25),
    ("Item2", 15),
    ("Item4", 20),
];

elemento_equipo.sort_by(|a, b| a.1.cmp(&b.1));

println!("Equipo ordenado por nivel: {:?}", elemento_equipo);

En este closure:

  • |a, b| a.1.cmp(&b.1) es una función anónima que compara el segundo pokémon (nivel) de las tuplas.

Uso de generics en funciones #

Los generics permiten que tus funciones trabajen con diferentes tipos.

Ejemplo: Función Genérica para Mostrar Información

fn mostrar_info<T: std::fmt::Debug>(pokemon: T) {
    println!("{:?}", pokemon);
}

mostrar_info(item4);
mostrar_info(equipo);

Aquí, mostrar_info puede aceptar cualquier tipo que implemente el trait Debug.

Práctica: simulando una batalla pokémon #

Pongamos en práctica lo aprendido creando funciones y utilizando tipos de datos.

struct Pokémon {
    nombre: String,
    tipo: String,
    nivel: u8,
    salud: i16,
    ataque: u16,
    defensa: u16,
}

fn ataque_elemento(atacante: &Pokémon, defensor: &mut Pokémon, poder: u16) {
    let dano = calcular_dano(atacante.ataque, defensor.defensa, poder);
    defensor.salud -= dano as i16;
    println!(
        "{} ataca a {} y causa {} puntos de daño.",
        atacante.nombre, defensor.nombre, dano
    );
}

fn main() {
    let item1 = Pokémon {
        nombre: String::from("Item1"),
        tipo: String::from("Eléctrico"),
        nivel: 25,
        salud: 100,
        ataque: 55,
        defensa: 40,
    };

    let mut item3 = Pokémon {
        nombre: String::from("Item3"),
        tipo: String::from("Agua"),
        nivel: 20,
        salud: 100,
        ataque: 48,
        defensa: 65,
    };

    ataque_elemento(&item1, &mut item3, 50);

    println!(
        "La salud de {} ahora es {}.",
        item3.nombre, item3.salud
    );
}

En este programa:

  • Definimos una estructura Pokémon con varios atributos.
  • Creamos una función ataque_elemento que simula un ataque entre dos Pokémon.
  • Usamos referencias y mutabilidad para modificar el estado del defensor.
  • Ejecutamos una batalla simple y mostramos los resultados.

Conclusión #

Los tipos de datos y las funciones son esenciales para cualquier programa en Rust. Entender cómo definir y utilizar diferentes tipos te permite modelar datos complejos, mientras que las funciones te ayudan a organizar y reutilizar el código.

Al usar ejemplos de Pokémon, hemos visto cómo estos conceptos se aplican en situaciones similares a las que podrías enfrentar en desarrollo real. Te animo a que sigas experimentando y creando tus propias funciones y estructuras.


¿Disfrutaste este artículo? ¡Intenta crear tus propios Pokémon y simula batallas entre ellos! Si tienes dudas o quieres compartir tus creaciones, ¡deja un comentario abajo!

Próxima vez en Rustaceo: Nos adentraremos en “Control de Flujo en Rust: Tomando Decisiones”, continuando nuestra aventura en el mundo de Rust y Pokémon.

¡Hasta la próxima, entrenadores!