Rusty Full Stack

El blog para los amantes de Rust, Ionic y Vuejs

Jaime Blandón
Jaime Blandón Desarrollador de software desde 2009, entusiasta de Rust, Vuejs y Ionic!. Fundador de este blog, espero que las publicaciones te sean de utilidad y si tienes comentarios para mejorar, son bienvenidos.

Manejo de Archivos de Texto Con Rust

En este post vamos a aprender a crear, escribir y leer archivos de texto con Rust. Pero ¿para qué aprender a manejar archivos de texto...?.

Desde la pre historia, la humanidad ha utilizado muchas maneras para registrar todo aquello que quiere recordar o transmitir. Desde las pinturas rupestres hasta publicaciones las redes sociales (o humildes blogs como este 🥺), todo es la representación de la transmisión de algún contenido.

Desde el inicio de la era de las computadoras digitales, se creó el concepto de "archivo virtual" para almacenar información que queremos que la computadora recuerde al momento que la encendemos.

Si has utilizado herramientas como Microsoft Excel, Word, PDF, incluso imágenes, todo este tipo de archivos, son representaciones en bytes de lo que queremos ver o analizar.

Aunque podría sonarte que los archivos de texto son algo que han pasado de moda, pues la verdad que todavía hoy en día se utilizan muy frecuentemente aunque no lo pareciera, por ejemplo cada publicación que haces en una red social, es almacenada en alguna base de datos que al final del día no es más que una estructuración de diferentes tipos de archivos binarios.

Muchos procesos de transferencia de información entre empresas, gobierno, bancos, etc, todavía utilizan archivos para intercambiarse datos, por lo que aprender a manejar archivos, es una habilidad esencial para cualquier programador.

Crear un archivo con Rust.

Vamos a hacer algunos ejemplos para la creación de archivos con Rust, voy a subir cada ejemplo como un archivo individual a nuestro repositorio de github, en tu caso puedes organizar el código como te facilite a ti seguir los ejemplos.

En alguna carpeta de tu preferencia crea un archivo llamado creacion_archivo.rs y colocaremos el siguiente código

        
        
            // creacion_archivo.rs

use std::fs::File;

fn main() -> std::io::Result<()>{
    File::create("ejemplo.txt")?;
    Ok(())
}

        
        
    

la línea use std::fs::File; se encarga de indicar a Rust que utilizaremos el módulo File, este es el módulo nativo de Rust con todas las opciones para el manejo de archivos, esto es muy útil sobre todo porque la gestión de archivos, depende bastante de tu configuración de Sistema Operativo, por ejemplo Windows maneja muy distinto los archivos a como los maneja linux o MacOS y el módulo nos facilita estas tareas porque nos permite utilizar todas sus funciones independientemente del sistema operativo que tengas.

La definición de la función main, también es un poco distinta a como lo hemos estado utilizado hasta la fecha

fn main() -> std::io::Result<()> todo aquello después de -> le hace saber a Rust el tipo de dato que espera que sea retornado, para este ejemplo le decimos a Rust que la función main retornará un "Resultado" del paquete io, nos quedará más claro más adelante.

La línea File::create("ejemplo.txt")?; es la encargada de crear el archivo, hay que notar que al final hay un signo de interrogación de cierre "?" lo cual indica que si crea el archivo, el programa continue con un Resultado de tipo io (te acuerdas de la declaración de la función main verdad? 😬) y si no logra crearlo por cualquier problema como permisos, espacio, etc, entonces el programa termina con un error.

La última línea Ok(()) es el retorno que espera main.

Si compilas el programa con:

rustc creacion_archivo.rs

Y lo ejecutamos con:

./creacion_archivo

Si todo ha salido bien, no verás ningún tipo de mensaje en la terminal, pero si miras los archivos dentro de la carpeta donde ejecutaste el programa, vas a encontrar un archivo nuevo ejemplo.txt.

Escribiendo en un archivo con Rust.

En el ejemplo anterior utilizamos la función create para generar nuestro archivo final llamado ejemplo.txt, si tuviste la oportunidad de abrirlo notarás que el archivo está vacío, ahora aprenderemos a escribir datos en el archivo. Dicho sea de paso, la función create, genera un archivo en modo write-only, esto quiere decir que al archivo, en ese momento, solamente puede escribírsele información sin la posibilidad de leerlo en ese momento.

Ahora escribamos este ejemplo para crear un archivo y escribirle algún contenido. en la carpeta de tu preferencia puedes crear un archivo llamado escribir_archivo.rs

        
        
            // escribir_archivo.rs

use std::fs::File;
use std::io::prelude::*;

fn main() -> std::io::Result<()> {

    // Este sera el contenido que vamos a escribir al archivo
    // Puedes generar alguno en bacon ipsum
    // https://baconipsum.com/ XD
    const CONTENIDO: &str = "Bacon ipsum dolor amet salami t-bone picanha jowl,
turducken pastrami fatback. Pig beef pancetta corned beef andouille rump ground round.
Sausage pork leberkas, drumstick turkey shankle brisket ball tip salami. Shoulder beef
pastrami venison bacon rump sirloin. Filet mignon venison flank ground round spare
ribs boudin shankle buffalo hamburger chislic. Kevin kielbasa corned beef tail burgdoggen
biltong salami, leberkas porchetta.";

    // Esta vez vamos a crear un archivo, pero
    // la referencia de su creacion la vamos a guardar
    // en una variable a la cual se le puede modificar el contenido.
    let mut archivo = File::create("ejemplo.txt")?;

    // Ahora vamos a escribir el contenido, es importante notar que el texto hay que pasarlo como
    // bytes, otra cosa importante a notar es que cuando el programa termine o se termine el scope
    // si fuera una funcion, entonces Rust cerrara el archivo por nosotros, no es necesario que lo
    // hagamos explicitamente
    archivo.write_all(CONTENIDO.as_bytes())?;

    Ok(())
}

        
        
    

El archivo anterior es bastante similar al de la creación, los cambios más importantes son los siguientes:

La línea use std::io::prelude::*; servirá para indicarle a Rust que nos permita utilizar las funciones de escritura.

el método:

write_all(CONTENIDO.as_bytes())?;

Escribirá en el archivo o disparará un error en caso la escritura falle, es importante notar que el String se debe convertir a bytes.

Si compilamos el archivo

rustc escribir_archivo.rs

Y lo ejecutamos

./escribir_archivo

Vamos ahora a ver que genera siempre el archivo ejemplo.txt, pero esta vez tiene el contenido que hemos especificado.

Modo Append!

Ahora, si ejecutas de nuevo el programa escribir_archivo, y vuelves a abrir el archivo ejemplo.txt, no vas a encontrar algún cambio, pero en realidad lo que Rust hace es sobre escribir con el mismo contenido que tenemos, esto es lo que significa abrir un archivo en modo "write", que el programa sobre escribirá los cambios.

Si en lugar de sobre escribir, lo que queremos es agregar al final del archivo el nuevo contenido debemos abrirlo en modo "append", con el siguiente ejemplo vamos a crear siempre el archivo ejemplo.txt, pero esta vez, por cada ejecución, el programa anexará el contenido al final del archivo.

para ello vamos a crear un archivo con nombre modo_append.rs con el siguiente código.

        
        
            // modo_append.rs

use std::fs::OpenOptions; // Esta linea nos brinda las opciones que necesitamos
use std::io::prelude::*;

fn main() -> std::io::Result<()> {

    // Este sera el contenido que vamos a escribir al archivo
    // Puedes generar alguno en bacon ipsum
    // https://baconipsum.com/ XD
    const CONTENIDO: &str = "Bacon ipsum dolor amet salami t-bone picanha jowl,
turducken pastrami fatback. Pig beef pancetta corned beef andouille rump ground round.
Sausage pork leberkas, drumstick turkey shankle brisket ball tip salami. Shoulder beef
pastrami venison bacon rump sirloin. Filet mignon venison flank ground round spare
ribs boudin shankle buffalo hamburger chislic. Kevin kielbasa corned beef tail burgdoggen
biltong salami, leberkas porchetta.";

    // Ahora en lugar de crear el archivo con el modulo File,
    // vamos a crearlo pero agregando nuevas opciones
    let mut archivo = OpenOptions::new()
                            .append(true) // Esta linea es la que especifica que queremos abrir el archivo en modo append
                            .create(true) // Esto le indica que si el archivo no existe, entonces que lo cree
                            .open("ejemplo.txt")?;

    // Ahora vamos a escribir el contenido, es importante notar que el texto hay que pasarlo como
    // bytes, otra cosa importante a notar es que cuando el programa termine o se termine el scope
    // si fuera una funcion, entonces Rust cerrara el archivo por nosotros, no es necesario que lo
    // hagamos explicitamente
    archivo.write_all(CONTENIDO.as_bytes())?;

    Ok(())
}

        
        
    

Si ejecutas el programa y el archivo ejemplo.txt no existe, entonces el programa lo creará con el contenido que hemos especificado, pero si el archivo ejemplo.txt existe, verás que el contenido es agregado al final del archivo.

Leyendo un Archivo con Rust.

Ahora que ya sabemos escribir archivos con rust, aprendamos a leerlos. En la carpeta que estás trabajando estos ejemplos, crea un archivo poema.txt y agrega el siguiente contenido.

        
        
            
Poema de Claudia Lars.

Te elevo sobre el mundo y el ensueño

Te elevo sobre el mundo y el ensueño,
¡escultura de luz, de aroma y canto!
Ala impaciente, roce de tu manto,
tácito y puro en vida y en diseño.

Te sostiene mi verso, tan pequeño
-piedra de espuma, base del encanto-
y en vigilias y vórtices de llanto
sierva soy al servicio de mi dueño.

Toda belleza en ti dobla su gracia,
toda gracia precisa sus virtudes,
toda virtud aumenta su eficacia.

Se alza de mi verdad tu nombre fuerte
y en espacio de soles y laúdes
quiebra el ángulo frío de la muerte.
        
        
    

Ahora creemos un archivo para leer el poema. en la misma carpeta donde tienes poema.txt crea un archivo de nombre leer_archivo.rs. Coloca el siguiente código, para este caso vamos a leer el contenido completo como un String:

        
        
            // leer_archivo.rs

use std::fs::File;
use std::io::prelude::*;

fn main() -> std::io::Result<()> {

    // Vamos a abrir el archivo en modo read only
    // para ello utilizamos el metodo open al cual
    // debemos pasarle la ruta del archivo que deseamos leer
    let mut archivo = File::open("poema.txt")?;
    let mut contenido = String::new();

    // Esta linea se encarga de leer el contenido completo
    // del archivo y lo coloca en un String, en este caso es
    // la mut variable contenido, el metodo read_to_string
    // recibe de parametro la referencia de la variable String
    // a la cual queremos pasarle el contenido del archivo
    archivo.read_to_string(&mut contenido)?;

    println!("Contenido del Archivo");
    println!("{}", contenido);

    Ok(())
}

        
        
    

Una de las diferencias con la escritura de un archivo es que utilizamos el método "open" el cual abre el archivo pero en modo read-only, si ahora compilamos y ejecutamos el programa:

rustc leer_archivo.rs

./leer_archivo

El programa abrirá el archivo poema.txt, colocará todo el contenido en una variable de tipo String, la muestra en pantalla y finalmente cerrará el archivo por nosotros para que se mantenga intacto.

Hacer un poco más eficiente la lectura de un archivo.

Con el ejemplo anterior hemos leído un archivo, pero según la documentación del módulo File, se nos recomienda leerlo utilizado un Buffer. Vamos a modificar un poquito nuestro código anterior para hacerlo con un buffer, siempre en nuestro archivo leer_archivo.rs, reemplacemos el código para agregar el buffer. Quedaría de la siguiente manera.

        
        
            // leer_archivo.rs

use std::fs::File;
// Agregando el modulo de Buffer para lectura
use std::io::BufReader;
use std::io::prelude::*;

fn main() -> std::io::Result<()> {

    // Vamos a abrir el archivo en modo read only
    // para ello utilizamos el metodo open al cual
    // debemos pasarle la ruta del archivo que deseamos leer
    let archivo = File::open("poema.txt")?;

    // Ahora creamos un buffer con el archivo, la ventaja de hacerlo
    // asi es que no debemos definir nuestro File como mutable, por
    // lo que es mas eficiente.
    let mut buf_reader = BufReader::new(archivo);
    let mut contenido = String::new();

    // Esta linea se encarga de leer el contenido completo
    // del archivo y lo coloca en un String, en este caso es
    // la mut variable contenido, el metodo read_to_string
    // recibe de parametro la referencia de la variable String
    // a la cual queremos pasarle el contenido del archivo
    buf_reader.read_to_string(&mut contenido)?;

    println!("Contenido del Archivo");
    println!("{}", contenido);

    Ok(())
}

        
        
    

Como puedes observar, ahora estamos introduciendo un nuevo módulo que también está incorporado por defecto en Rust

use std::io::BufReader;

Si observas otro cambio importante con respecto al código anterior, es que en la definición del archivo, cuando no usamos buffer, lo tuvimos que especificar en modo mut por lo que Rust debe manejar la memoria de forma distinto que a una constante, esta vez no utilizamos mut, por lo que el manejo de memoria está optimizado.

 let mut buf_reader = BufReader::new(archivo);

Por último la lectura del archivo se hace siempre con el método read_to_string, pero esta vez lo hacemos utilizando el buffer

 buf_reader.read_to_string(&mut contenido)?;

Leer un Archivo Línea por Línea con Rust.

En el ejemplo anterior leímos un archivo, pero todo el contenido es colocado en una variable, pero hay ocasiones en las cuales necesitamos leer el archivo línea a línea, vamos a leer siempre el archivo poema.txt, pero esta vez lo vamos a imprimir línea por línea. Otra ventaja de los buffers, es que nos permiten realizar esta acción, creemos un nuevo archivo llamado leer_archivo_por_linea.rs

        
        
            // leer_archivo_por_linea.rs

use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() -> std::io::Result<()> {

    // Vamos a abrir el archivo en modo read only
    // para ello utilizamos el metodo open al cual
    // debemos pasarle la ruta del archivo que deseamos leer
    let archivo = File::open("poema.txt")?;

    // Ahora creamos un buffer con el archivo
    let buf_reader = BufReader::new(archivo);

    println!("Contenido del Archivo");

    // Ahora vamos a leer el archivo, pero imprimiremos
    // su contenido linea por linea.
    for linea in buf_reader.lines() {
        // Hay que notar el unwrap() para que devuelva el valor.
        // En caso este sea None, entonces el programa terminaria
        // con un error, por suerte esto lo tenemos controlado.
        println!("{}", linea.unwrap());
    }

    Ok(())
}

        
        
    

Como puedes ver, leer un archivo línea por línea es bastante fácil porque el buffer nos provee de un método llamado lines.

Únicamente hay que notar que lines() retorna un vector de Option<String>, esto quiere decir que la línea podría ser None, para nuestro caso no nos interesa mucho validarlo porque tenemos el control sobre nuestro archivo. Si en tu caso no tienes ese control, debes validar la línea antes de imprimirla, esto lo puedes hacer chequeando el OK() o con el método is_none() para la línea.

Hasta acá llegamos con los ejemplos, si quieres ver todos los códigos puedes hacerlo desde el repositorio con el material de esta publicación dando click acá.

Si quieres aprender más sobre archivos, te dejo el enlace a la documentación del módulo de File

Espero esta publicación te haya sido de utilidad, si te ha gustado compártela con tus amigos y en tus redes sociales para que todos podamos manejar mejor archivos con Rust.

println!("Hasta la próxima!!");


 Utilizamos cookies propias y de terceros para mejorar tu experiencia, mostrar publicidad y análisis de navegación, puedes encontrar el detalle en nuestra Política de Cookies