Lección 04 de 26 Primeros pasos
Funciones y expresiones
- Una función es una máquina: recibe datos, hace su trabajo y devuelve un resultado. Se declara con fn.
- Los parámetros llevan tipo obligatorio: n: i32. Al llamar a doble(21), el 21 entra como n.
- -> i32 dice qué devuelve: la última expresión, n * 2, escrita sin punto y coma. ¡Sale un 42!
- Con n * 2; la línea pasa a ser una sentencia: la función devolvería () en vez de un i32 y no compila.
Desde la primera lección has usado una función: main. Ahora vas a crear las tuyas, a pasarles datos y a devolver resultados.
Por el camino descubrirás una de las ideas más importantes de Rust: la diferencia entre sentencias y expresiones. Cuando la entiendas, sabrás por qué a veces un simple punto y coma hace que tu código no compile.
Declarar una función
Las funciones se declaran con fn, un nombre, unos paréntesis y un cuerpo entre llaves. Para llamarlas, escribe su nombre seguido de paréntesis:
fn main() {
saludar();
saludar();
}
fn saludar() {
println!("¡Hola desde una función!");
}- El orden no importa: puedes declarar
saludarantes o después demain. - Los nombres de las funciones van en
snake_case, igual que las variables:calcular_total, nocalcularTotal.
Parámetros
Los parámetros son los datos que recibe la función. En Rust el tipo de cada parámetro es obligatorio:
fn main() {
mostrar_puntos("Ferris", 120);
mostrar_puntos("Corro", 95);
}
fn mostrar_puntos(nombre: &str, puntos: u32) {
println!("{nombre} tiene {puntos} puntos");
}Dentro de una función Rust infiere los tipos, pero en la firma (la primera línea) no lo hace, y es a propósito: la firma es un contrato. Quien la lee sabe qué recibe la función sin mirar el cuerpo, y el compilador puede darte errores más claros. Si olvidas un tipo, no compila:
fn doble(n) -> i32 {
n * 2
}
fn main() {
println!("{}", doble(4));
}Devolver un valor
El tipo que devuelve la función se indica con una flecha -> después de los parámetros. El valor devuelto es la última expresión del cuerpo, escrita sin punto y coma:
fn doble(n: i32) -> i32 {
n * 2
}
fn main() {
let resultado = doble(21);
println!("El doble de 21 es {resultado}");
}No hace falta escribir return: n * 2 es lo último que se evalúa y ese es el valor de la función. Para entender por qué funciona así, tienes que distinguir dos cosas.
Sentencias y expresiones
- Una sentencia realiza una acción y no produce ningún valor.
let x = 5;es una sentencia. - Una expresión se evalúa a un valor.
5,x + 1,doble(3)ox > 0son expresiones.
Como let es una sentencia, no puedes escribir let a = (let b = 1);, algo que en otros lenguajes sí funciona.
En Rust casi todo es una expresión, incluso un bloque entre llaves. Su valor es el de la última expresión que contiene:
fn main() {
let y = {
let x = 3;
x + 1
};
println!("y vale {y}");
}El bloque vale 4 porque x + 1 no lleva punto y coma. Si se lo pones, se convierte en una sentencia y el bloque deja de devolver ese valor:
El cuerpo de una función también es un bloque. Por eso su última expresión es el valor que devuelve.
El tipo unit ()
¿Y qué vale un bloque que termina en una sentencia? Vale (), la tupla vacía, que se llama unit. Es la forma que tiene Rust de decir «aquí no hay nada que devolver».
Las funciones sin -> devuelven () de forma implícita. Estas dos funciones son equivalentes:
fn saludar() {
println!("Hola");
}
fn saludar_explicito() -> () {
println!("Hola");
}El error clásico: un punto y coma de más
Ya puedes entender el error más típico al empezar con Rust. Pulsa Ejecutar:
fn doble(n: i32) -> i32 {
n * 2;
}
fn main() {
println!("{}", doble(5));
}error[E0308]: mismatched types
--> src/main.rs:1:21
|
1 | fn doble(n: i32) -> i32 {
| ----- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | n * 2;
| - help: remove this semicolon to return this valueCon el ;, n * 2 pasa a ser una sentencia, el cuerpo termina sin expresión y la función devolvería () en lugar del i32 que promete. El compilador lo detecta y te señala exactamente qué punto y coma sobra.
return para salir antes
También existe return, que sale de la función en ese mismo momento con el valor que le indiques. Se usa sobre todo para salir antes de tiempo en un caso especial:
fn dividir(a: i32, b: i32) -> i32 {
if b == 0 {
return 0;
}
a / b
}
fn main() {
println!("{}", dividir(10, 2));
println!("{}", dividir(10, 0));
}Verás if a fondo en la siguiente lección, pero la idea se entiende: si b es cero, la función devuelve 0 sin llegar a dividir. En el resto de casos, devuelve la última expresión, a / b.
Estilo: Lo idiomático en Rust es usar
returnsolo para salidas tempranas. Al final de la función, deja la última expresión sin punto y coma.
Devolver varios valores
Una función devuelve un único valor, pero ese valor puede ser una tupla. Así devuelves varios datos a la vez:
fn rectangulo(ancho: u32, alto: u32) -> (u32, u32) {
let area = ancho * alto;
let perimetro = 2 * (ancho + alto);
(area, perimetro)
}
fn main() {
let (area, perimetro) = rectangulo(3, 4);
println!("Área: {area}, perímetro: {perimetro}");
}Fíjate en el cuerpo de rectangulo: dos sentencias let y, al final, una expresión sin punto y coma, la tupla, que es lo que devuelve.
Hora de practicar
Ejercicios
0/4 resueltos
Reto 1 de 4 Resuelto
cuadrado no compila. Arréglala sin usar return para que devuelva el número multiplicado por sí mismo.Al ejecutar, unos tests ocultos comprueban tus funciones.
Pulsa Ejecutar para compilar y ver aquí la salida de tu programa.