Introducción a Rust

Versión y ediciones

El lenguaje Rust utiliza tres conceptos de versión independientes: versionado semántico, canales de lanzamiento y ediciones (editions).

Versionado semántico

El formato es: versión_mayor.versión_menor.versión_parche, separados por puntos. Las reglas de incremento son: la versión mayor cambia por modificaciones incompatibles en la API; la versión menor por nuevas funcionalidades compatibles con versiones anteriores; y la versión parche por correcciones de errores compatibles.

Canales de lanzamiento

Los canales disponibles son: Nightly (a partir de la rama master), Beta (a partir de beta) y Stable (a partir de stable).

Ediciones

Las ediciones incluyen: Edición 2015, Edición 2018 y Edición 2021.

Instalación del entorno

Para instalar Rust, descargue el instalador rustup-init desde el sitio oficial. Ejecute el instalador y siga las instrucciones. Para verificar la instalación, abra una terminal y ejecute:

rustc --version
cargo --version

Si se muestran las versiones, la instalación fue exitosa. Para compilar y ejecutar un programa, cree un archivo principal.rs con código Rust y use cargo run en la terminal.

Entrada y salida

Salida

Para imprimir en la consola, se usa println!. Para almacenar salida en un String, se usa format!.

println!("Hola, mundo");
let mensaje = format!("Saludo: {}", "hola");

Entrada

Para leer una línea desde la entrada estándar:

use std::io::{self, BufRead};

fn leer_linea() -> String {
    let mut buffer = String::new();
    let stdin = io::stdin();
    stdin.lock().read_line(&mut buffer).expect("Error al leer");
    buffer.trim().to_string()
}

Para leer todo el contenido hasta el final del archivo:

fn leer_todo() -> String {
    let mut contenido = String::new();
    io::stdin().lock().read_to_string(&mut contenido).expect("Error al leer");
    contenido
}

Funciones

Función principal

La función main es el punto de entrada del programa.

fn main() {
    println!("Programa iniciado");
}

Funciones regulares

Las funciones pueden no retornar valor (tipo unitario) o retornar un valor específico. El valor de retorno se puede omitir con return o usar la última expresión sin punto y coma.

fn calcular_suma(a: i32, b: i32) -> i32 {
    a + b
}

fn mostrar_resultado(valor: i32) {
    println!("Resultado: {}", valor);
}

Llamada a funciones externas

Para llamar a funciones de otros lenguajes como C, se usa el bloque unsafe.

extern "C" {
    fn abs(valor: i32) -> i32;
}

fn usar_externa() {
    let numero = -5;
    let absoluto = unsafe { abs(numero) };
    println!("Valor absoluto: {}", absoluto);
}

Expresiones lambda

Las clausuras inferen tipos de parámetros en la primera llamada. Ejemplo con error corregido:

fn main() {
    let duplicar = |x: i32| x * 2;
    let resultado = duplicar(10); // Tipo i32 inferido
    println!("Resultado: {}", resultado);
    // Si se usa con otro tipo, causará error de compilación.
}

Identificadores y variables

Identificadores

Los identificadores en Rust pueden usar una amplia gama de caracteres Unicode. Se pueden usar identificadores crudos con el prefijo r# para palabras reservadas.

let nombre = "Rust";
let r#tipo = 42; // Usando identificador crudo para 'tipo' (keyword reservada en otros contextos)

Enlace de variables y mutabilidad

Las variables se enlazan con let y son inmutables por defecto. Para hacerlas mutables, se usa let mut.

let valor = 10; // Inmutable
let mut contador = 0; // Mutable
contador += 1;

Ámbito de variables

Las variables tienen ámbito limitado por bloques. Se pueden reutilizar nombres en ámbitos internos.

fn main() {
    let x = 5;
    let x = x + 1; // Reutilización de nombre
    {
        let x = x * 2; // Nuevo ámbito
        println!("x interno: {}", x); // Imprime 12
    }
    println!("x externo: {}", x); // Imprime 6
}

Palabras clave

Rust tiene palabras clave estrictas (como fn, let), reservadas (como abstract, become) y débiles (como union en contextos específicos).

Tiempos de vida (lifetimes)

Los tiempos de vida aseguran que las referencias sean válidas. Se anotan con apóstrofos.

fn encontrar_maximo<'a>(a: &'a i32, b: &'a i32) -> &'a i32 {
    if a > b { a } else { b }
}

struct Contenedor<'a> {
    referencia: &'a i32,
}

fn main() {
    let num = 10;
    let cont = Contenedor { referencia: &num };
}

Reglas de inferencia: para funciones con un solo parámetro de referencia o con self, el tiempo de vida del retorno se infiere automáticamente. Ejemplo de inferencia automática:

fn identidad(s: &str) -> &str {
    s
}

Expresiones y control de flujo

Expresión match

match permite emparejar valores con patrones. Debe cubrir todos los casos posibles, usando comodines si es nceesario.

fn main() {
    let valor = 3;
    let resultado = match valor {
        1 => "uno",
        2 | 3 => "dos o tres",
        x if x > 10 => "mayor que diez",
        _ => "otro",
    };
    println!("{}", resultado);
}

Los bloques de match pueden retornar valores de diferentes tipos, pero deben ser consistentes.

Convenciones de programación

Formato de nombres

  • CamelCase: para tipos, traits y enumeraciones.
  • snake_case: para funciones, métodos, variables y módulos.
  • SCREAMING_SNAKE_CASE: para constantes y variables estáticas.

Nombres comunes

Funciones constructoras se nombran con with_*, funciones de conversión con from_*, y prefijos como as_, to_, into_ indican diferentes costos de memoria.

Tipos cnostantes

Los tipos de constantes no deben tener mutabilidad interna, como Cell o Mutex.

Conversiones de tipo

Para conversiones de alto a bajo rango, usar try_from; de bajo a alto, usar from.

Numerales

Los literales numéricos deben especificar tipos explícitamente para evitar ambigüedades.

FFI (Interfaz de funciones externas)

Al llamar a funciones C, evitar funciones no reentrantes o inseguras en memoria.

Diferenciación de nombres homónimos

vec vs Vec

vec es un módulo y una macro, mientras que Vec es un tipo de datos. Se distinguen por contexto.

use std::vec::Vec;
let mi_vec: Vec<i32> = vec![1, 2, 3];</i32>

Funciones homónimas

Un tipo puede tener múltiples funciones con el mismo nombre, resolviéndose por métodos o conversiones implícitas.

Traits y funciones independientes

Por ejemplo, la función drop del trait Drop y la función genérica drop se diferencian por el contexto de uso.

Tipos homónimos en estándares

Diferentes módulos pueden definir tipos con el mismo nombre, como Result en std::fmt versus std::result.

use std::fmt::Result as FmtResultado;
let r: FmtResultado = Err(std::fmt::Error);

Etiquetas: Rust rust-lang cargo ownership lifetimes

Publicado el 6-13 19:12