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);