Saltar al contenido principal

Funciones

  • Siempre se espera una función principal, fn main(){}
  • Los nombres de las funciones o variables deben ser con snake_case o kebab-case
  • Hoisting, la función puede ser definida y/o llamada en cualquier lugar
fn main() {
    println!("Main function");
   
    hello_rust();
}

// Hoisting, the function can be called before being declared
fn hello_rust() {
    println!("Hello, Rust");
}

Parámetros

  • Argumentos (variables) que forman parte de la firma de una función
  • En la firma de la función se debe declarar el tipo de cada parámetro
fn main() {
    another_function(5);
    person("Lucas Primero", 25, 175.0);
}

fn another_function(x: i32) {
println!("The value of x is: {x}");
}

fn person(name: &str, age: u32, height: f32) {
    println!("--- Personal data ---");
    println!("Name: {}, Age: {}, Height: {} cm.", name, age, height);
}

Declaraciones y Expresiones

El cuerpo de una función están formados por una serie de instrucciones que, opcionalmente, terminan en una expresión

Declaraciones (Statements)

  • Las statements o declaraciones son instrucciones que realizan alguna acción y no devuelven un valor
  • La definición de variables y funciones son ejemplos de declaraciones.
  • La definición de una función es una declaración, pero cuando se llama a la función, esa llamada se convierte en una expresión porque devuelve un valor
  • Generalmente terminan con ;, lo que las diferencia de las expresiones

Expresiones (Expressions)

  • Las expresiones se evalúan y devuelven un valor resultante
  • Pueden formar parte de una sentencia (statement), ser asignadas a variables o usarse dentro de otras expresiones.
  • Llamar a una función, una macro o ejecutar un bloque de código son ejemplos de expresiones
  • No terminan con ; ni requieren return para devolver un valor
  • Si se incluye punto y coma al final, se convierte en un statement y por lo tanto no devolverá un valor
  • Las estructuras de control como if, match y bloques {} pueden ser expresiones.

Ejemplos

  • Un bloque de código se evalúa y devuelve un valor asignado a y
fn main() {
let y = {
let x = 5;
x * 2 // Esta es la expresión que se evalúa y devuelve 10
};

println!("The value of y is: {y}"); // The value of y is: 10
}
  • El resultado de un if puede asignarse a una variable
fn main() {
let temperature = 15;
let weather = if temperature > 25 { "hot" } else { "cold" };

println!("The weather is {weather}"); // The weather is cold
}
  • El cuerpo de una función en Rust puede consistir en una sola expresión, sin necesidad de return
// Definir la función es una declaración
fn square(n: i32) -> i32 {
n * n // Expresión que devuelve el resultado
}

fn main() {
// Statement: define una variable, pero no devuelve un valor
let num = 4;
// Llamar a la función es una expresión, porque devuelve un valor
let result = square(num);

println!("The square of {num} is {result}"); // The square of 4 is 16
}
fn main() {
    let weight = 70.0;
    let height = 1.80;
    let bmi = calculate_bmi(weight, height);
    println!("El BMI es: {:.2}", bmi); // {:.2} para mostrar solo 2 decimales
}

// BMI = height(kg)/height(m)^2
fn calculate_bmi(weight_kg: f64, height_m: f64) -> f64 {
    // expression dentro del cuerpo de la función (statement)
    weight_kg / (height_m * height_m)
}

Funciones con valores de retorno

  • Se usa -> Type para indicar el tipo de dato que devuelve la función
  • No es obligatorio usar la palabra return, ya que la última expresión dentro de la función se considera el valor de retorno
  • La última expresión del cuerpo de la función no debe terminar con ;, ya que convertiría la expresión en una declaración sin valor de retorno
  • Se puede salir de forma temprana de una función utilizando return y especificando un valor o no
  • Además de tipos primitivos (i32, f64, bool, etc.), las funciones pueden devolver tipos más complejos como tuplas (x, y, z), Option<T> y Result<T, E>,

Retorno implícito

fn main() {
let x = five();
println!("The value of x is: {x}");
}

fn five() -> i32 {
5
}
fn main() {
    let result = add(5, 3);
   
    println!("5+3: {}", result);
    println!("1+2: {}", add(1, 2));
}

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

Retorno temprano con return

fn check_number(n: i32) -> &'static str {
if n < 0 {
return "Negative"; // Retorno temprano
}

"Positive or zero" // Última expresión, retorna implícitamente
}

fn main() {
println!("{}", check_number(-5)); // Imprime: Negative
println!("{}", check_number(10)); // Imprime: Positive or zero
}
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
return None; // Salida inmediata si b es 0
}

Some(a / b) // Última expresión, se devuelve automáticamente
}

Devolviendo tuplas (x, y, z)

fn calculate_circle(radius: f64) -> (f64, f64) {
let area = std::f64::consts::PI * radius * radius;
let circumference = 2.0 * std::f64::consts::PI * radius;

(area, circumference)
}

fn main() {
let radius = 5.0;
let (area, circumference) = calculate_circle(radius);

println!("Área: {:.2}, Circunferencia: {:.2}", area, circumference);
}

Devolviendo Option<T> para valores opcionales

fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None // Retorna `None` si la división no es válida
} else {
Some(a / b) // Retorna `Some(valor)` si la división es válida
}
}

fn main() {
// Accediento a un Option<T> con match
match divide(10.0, 2.0) {
Some(result) => println!("Resultado: {result}"),
None => println!("No se puede dividir por cero"),
}

// Accediento a un Option<T> con if
if let Some(res) = divide(10.0, 0.0){
println!("Resultado: {}", res);
} else {
println!("No se pudo dividir");
}
}

Devolviendo Result<T, E> para manejo de errores

fn safe_divide(a: f64, b: f64) -> Result<f64, &'static str> {
if b == 0.0 {
// Devuelve un error si `b` es 0
Err("Error: división por cero")
} else {
// Devuelve el resultado si es válido
Ok(a / b)
}
}

fn main() {
match safe_divide(10.0, 2.0) {
Ok(result) => println!("Resultado: {result}"),
Err(error) => println!("{error}"),
}
}