Leer datos desde teclado, usando Rust

7 min(s) Fecha: 2022-06-23

🦀 Utilizando varios tipos de datos.

Una de las cosas más triviales al momento de desarrollar una aplicación por línea de comandos, es la capacidad de leer la información ingresada por el teclado.

El cuento no para ahí en solo leer lo que el usuario ingrese, si no también determinar que información ingresa y poder asignar el tipo de dato correcto a esa entrada.

Una forma Quick-and-dirty sería la siguiente:

use std::io;

...

fn main() {
    println!("ingresa un dato");
    let mut input = String::new();

    io::stdin().read_line(&mut input);
    
    println!("el dato ingresado es: {}", input.trim());
}

después limpiar un poco la entrada utilizando trim, una función integrada en Rust que se utiliza para recortar los espacios en blanco iniciales y finales de una cadena, nuestro dato está listo para ser utilizado.

sin embargo si la entrada es numérica...., recuerda que la variable input fué inicialida como String y toca trabajo adicional.

use std::io;

...

fn main() {
    println!("ingresa un dato");
    let mut input = String::new();

    io::stdin().read_line(&mut input);

    println!("el dato ingresado es: {}", input.trim());
    println!();

    let result = match input.trim().parse::<u32>() {
        Ok(parsed_input) => parsed_input,
        Err(_) => {
            println!("el dato ingresado no es un numero, verifica");
            0
        }
    };

    println!();
    println!("el dato ingresado despues de parsearlo como u32: {}", result);

}

utilizando .parse::<u32>() convertimos nuestra entrada(String) a un tipo de datos numérico(u32), si la conversión falla, por defecto asignamos el valor de cero(0).

si ingresamos una cadena de texto, la salida es algo como:

❯ cargo run
ingresa un dato
ola ke ase
el dato ingresado es: ola ke ase

el dato ingresado no es un numero, verifica

el dato ingresado despues de parsearlo como u32: 0

y en el caso de ser un número...

❯ cargo run
ingresa un dato
250
el dato ingresado es: 250

el dato ingresado despues de parsearlo como u32: 250

hacemos un cambio al agregar, stdout().flush() antes de leer la entrada desde el teclado, y manejamos los errores que se puedan generar.

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

...

println!("ingresa un dato");
let mut line = String::new();

let _ = io::stdout().flush().expect("Failed to flush stdout.");

io::stdin().read_line(&mut line).expect("Failed to read input.");

...

Al utilizar stdout, recuerda que suele tener un búfer de línea por defecto, por lo que puede ser necesario utilizar io::stdout().flush() para asegurarse de que la salida se emita inmediatamente.


en cristiano por favor.
-- un visitante del blog

Claro, limpiamos el flujo de salida, para asegurarnos que todos los contenidos almacenados en el buffer intermedio lleguen a su destino. y despues hacemos la llamada a stdin().read_line

más info en std::print y flush.


Excelente, pero si se desea leer datos desde el teclado y parsearlos a diferente tipo, esta solución toca hacerle mejoras cierto?
-- un visitante del blog

Claro que sí, se detalla a continuación.


Mejorando la lectura datos desde teclado

A nuestro ejemplo, lo convertiremos en una función genérica.

El uso más simple y común de las funciones genéricas en Rust, es para "los parámetros de tipo", que se especifican como genéricos mediante el uso de paréntesis angulares(<>) y mayúsculas: <Aaa, Bbb, ...>.

Los "parámetros de tipo genérico" se representan típicamente como < T >. Además de requerir una sintaxis en algunos casos, bastante complicada.

Texto tomado de Rust By Example
fn generic_function<T>(foo: T) -> T {
    foo
}

Sí, Este tema es un poco complejo inicialmente, recomendamos profundizar en el tema.

En Rust, "genérico" también describe cualquier cosa que acepte uno ó más parámetros de tipo genérico < T >. Cualquier tipo especificado como parámetro de tipo genérico es genérico, y todo lo demás es concreto (no genérico).💥🌀

WTF!
-- un visitante del blog

Sí, todos estamos confundidos, continuemos.

Finalmente le agregaremos un ciclo a nuestra función, Rust proporciona la palabra clave "loop" para indicar un bucle infinito.

loop {
    // do stuff
}
Ehh, no es ya un poco complejo hasta aquí, para que un ciclo infinito?
-- un visitante del blog

Esto permite, que en el caso de utilizar un tipo de dato para leer la entrada, y el usuario escriba otra cosa, no acorde al tipo de dato, la función vuelva a esperar la entrada, Ejemplo:

se define un tipo de dato como usize y el usuario escribe "hola mundo"...

nuestra función hará lo siguiente:

Todos, seguimos confundidos, continuemos...
-- un visitante del blog

Y... se aclara a continuación:

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

...

fn get_input_default<U: std::str::FromStr>(prompt: &str) -> U {
    loop {
        let mut line = String::new();

        print!("{}", prompt);

        let _ = io::stdout().flush().expect("Failed to flush stdout.");

        io::stdin().read_line(&mut line).expect("Failed to read input.");

        let input_trimmed = line.trim();

        let input = match input_trimmed.parse::<U>() {
            Ok(parsed_input) => parsed_input,
            Err(_) => {
                println!("el dato ingresado es incorrecto, verifica");
                continue;
            }
        };

        return input;
    }
}

Modo de uso

Utilizamos varios tipos de datos para leer la entrada desde el teclado.

fn main() {
    let opcion_u32: u32 = get_input_default("leer como u32: ");
    let opcion_string: String = get_input_default("leer como String: ");
    let opcion_u8: u8 = get_input_default("leer como u8: ");
    let opcion_usize: usize = get_input_default("leer como usize: ");
    let opcion_bool: bool = get_input_default("leer como bool: ");
    let opcion_char: char = get_input_default("leer como char: ");

    println!();
    println!("el dato leido como u32: {}", opcion_u32);
    println!("el dato leido como string: {}", opcion_string);
    println!("el dato leido como u8: {}", opcion_u8);
    println!("el dato leido como usize: {}", opcion_usize);
    println!("el dato leido como bool: {}", opcion_bool);
    println!("el dato leido como char: {}", opcion_char);
}

y la salida luce como lo siguiente:

❯ cargo run
leer como u32: 34
leer como String: ola ke ase
leer como u8: 400
el dato ingresado es incorrecto, verifica
leer como u8: 250
leer como usize: -1
el dato ingresado es incorrecto, verifica
leer como usize: 100
leer como bool: False
el dato ingresado es incorrecto, verifica
leer como bool: false
leer como char: ZA
el dato ingresado es incorrecto, verifica
leer como char: Z

el dato leido como u32: 34
el dato leido como string: ola ke ase
el dato leido como u8: 250
el dato leido como usize: 100
el dato leido como bool: false
el dato leido como char: Z

muy muy interesante!
-- un visitante del blog

Referencias