Control de flujo en Rust – Aprende Rust 5/x

Como en otros lenguajes, el control de flujo en Rust es vital para decirle a nuestros programas los bloques de código que tiene que ejecutar de acuerdo a la entrada de datos. Aquí mencionaremos algunas cosas relacionadas con nuestro primer ejemplo.

if y else en Rust

Si hay algo que dicta el control de flujo en Rust es if, una de las keywords más usadas en todos los lenguajes de programación y se usa de la misma manera en todos. Esta keyword nos permite ejecutar un bloque de código que coincida con la condición que hemos establecido. if es una expression es decir, regresa un valor después de evaluar la condición.

Su uso es de la siguiente manera:

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

En este ejemplo, declaramos number y le asignamos el valor 3 (esta variable no se puede modificar porque no hemos puesto mut). Después, mediante if le decimos que si la variable es menor a 5, es decir, 4 o menor, entrará al primer statement. De lo contrario, usamos else para indicarle que si es 5 o mayor entrará a ese bloque.

Ahora veamos este ejemplo:

fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}

Si tratamos de compilar con cargo build. El programa nos dará un error “expected bool, found integer”.

A diferencia de otros lenguajes que verifican si la variable “tiene un valor que no regrese false”, por ejemplo, un 1, true o manuel, en Rust debes establecer en el if una condición que regrese un valor bool, no le basta con que la variable tenga un valor válido, valga la redundancia.

fn main() {
    let number = 3;

    if number != 0 {
        println!("number was something other than zero");
    }
}

Este sería un código válido, ya que se evalúa si number es diferente de cero, independientemente si el valor es mayor o menor, solo debe ser diferente de cero.

else if en Rust

Podemos hacer una serie de concatenaciones de ifs, usando else if.

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

En este ejemplo podemos ver que hacemos algunas condiciones para ejecutarse según qué casos apliquen. Revisamos si number es divisible entre 4, 3 o 2, en caso de no serlo en ningún caso, entrará al último bloque else, donde nos indica que number no cumple con ninguna condición.

Uso de if y let

Recordemos que en el capítulo anterior definíamos las expressions y los statements:

  • expressions: evalúan una operación y regresan un valor.
  • statements: ejecutan una acción sin regresar un valor.

Como if es una expression podemos usar el valor que regresa para usarlo y asignarlo, por ejemplo a una variable o como nuestro return en una función:

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}

Al ser condition un valor de tipo bool, podemos usarlo para evaluar la condición y nuestro if no tendrá problemas. Entonces ¿qué valor se asigna a number? Piensa, piensa.

Acá aprovecharemos para revisar algo importante dentro del uso de las variables y tratar de crear algo equivalente.

fn main() {
    let condition = false;
    let mut number = 1;

    if condition {
        number = 5;
    } else {
        number = 10;
    }

    println!("The value of number is: {}", number);
}

¿Esto parece bastante legítimo no? El resultado de cargo build es el siguiente.

control de flujo en rust warning

warning: value assigned to number is never read

¿Warnings? ¿por qué nos dice que no la hemos leído si ya la hemos leído en nuestro println!? Bueno, esto es porque una vez que declaramos la variable, le asignamos otros valores, pero no hacemos nada con ella antes de cambiarle el valor.

El flujo es: la declaramos, le asignamos un nuevo valor y después la imprimimos. Para Rust esto se puede compilar y ejecutar, pero te dice que posiblemente tienes una variable a la que no le has dado uso. Editemos un poco el código:

fn main() {
    let condition = false;
    let mut number = 1;

    println!("The value of number is: {}", number);

    if condition {
        number = 5;
    } else {
        number = 10;
    }
}

Ahora tenemos dos warnings “warning: value assigned to number is never read”, una por cada condición. Al igual que antes, nos dice que no hemos usado la variable number. Comprobamos que en Rust necesitas usar las variables una vez que su valor cambia.

Podemos concluir que la opción original sería la mejor si queremos hacer algo así. OJO, nuestra aplicación funcionará, pero esos warnings se ven feos, la solución final sería:

fn main() {
    let condition = false;
    let mut number = 1;

    println!("The value of number is: {}", number);

    if condition {
        number = 5;
    } else {
        number = 10;
    }

    println!("The value of number is: {}", number);
}

Aquí se declara y se usa, luego se cambia el valor y se vuelve a usar.

Ciclos en Rust (loops)

Rust tiene tres tipos de loops:

  • loop
  • while
  • for

loop

loop es un ciclo que no termina hasta que dentro del bloque de código, el algún lugar indiquemos que debe finalizar, como en nuestro ejemplo del segundo post:

fn main() {
    loop {
        println!("again!");
    }
}

Este código nunca finaliza, hasta que manualmente terminemos el programa, cerrando la ventana o dando ctrl + c.

Para terminar el ciclo dentro de la propia ejecución sin tener que intervenir usamos la palabra break.

Hay un curioso uso de loops, veamos un ejemplo:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

En la línea de break counter * 2, le decimos al programa que cuando por fin salgamos del ciclo le enviaremos el valor de counter multiplicado por 2. Este valor es 20 ya que en nuestra condición nos indica que entrará a ese bloque cuando counter sea igual a 10.

Por default, el uso de break y continue se limita al bloque de código en el que se llama, de adentro hacia afuera. En Rust tenemos una forma de hacer labeling e indicarle cuál loop debemos romper:

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

Aquí vemos que llamamos 'counting_up al primer loop. Ese label nos ayudará a decirle dentro del segundo loop que cuando count sea igual a 2 vamos a salir, pero no del segundo bloque, sino del primero, es decir, ahí termina la ejecución del programa.

while

while evalúa una condición para determinar si el ciclo continúa o se detiene y lo hace desde la primera vez que se ejecuta. Para terminar el ciclo esta condición debe ser true. Aquí tenemos un ejemplo:

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

En este ejemplo, declaramos number con un valor de 3. La primera vez, while evalúa si number es diferente de 0, imprime le número y después le resta 1, repite esto mientras number sea diferente de 0. Una vez que el valor sea cero, el ciclo termina e imprime LIFTOFF!!!.

for

for en Rust, por lo general es usado para recorrer elementos dentro de un array, ya que nos aseguramos de forma segura que no leeremos un index incorrecto.

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

Aquí recorremos el array a imprimiendo primero el 10y finalizando con el 50.

También podemos usar rangos, aquí tenemos un ejemplo para que lo tengas aunque no hemos revisado rev, pero lo que hace es que le damos un rango de números del 1 al 4, sin incluir el último, si lo queremos incluir sería (1..=4) como lo vimos en nuestro primer programa. rev lo que hace simplemente darte el orden inverso:

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

Hasta aquí dejaremos el post de control de flujo en Rust, no sin antes mencionar que en Rust no existe el switch, pero lo hemos usado antes con match, un ejemplo:

fn main() {
    let number = 3;

    match number {
        1 => println!("one"),
        2 | 3 => println!("two or three"),
        4..=6 => println!("range from 4 to six (inclusive)"),
        _ => println!("default"),
    }
}

Puedes leer acá para conocer más: https://doc.rust-lang.org/book/ch03-05-control-flow.html

Gracias por leer.


Posted

in

, , ,

by