Crates en Rust – Separar código 2/2 – Aprende Rust 15/x

separa código en rust

Dentro de los crates en Rust se pueden hacer públicos y privados estructuras de información como structs y enums. En el post anterior vimos algunos aspectos generales sobre los crates. Aquí aprenderemos algo fundamental que es separar código y módulos para hacer más mantenible nuestro código.

Structs y Enums

Para los structs, los permisos son granulares, como hemos visto hasta ahora. Es decir, si tenemos el siguiente struct:

// ...
mod factura {
    mod crear_factura {
        pub struct producto {
            id: u64,
            nombre: String,
            precio: f32,
        }
        pub fn sellar() {}
        fn agregar_addenda() {}
    }

    pub mod timbrar {
        // ...
    }
}
// ...

Aquí el struct es público, pero sus miembros no.

Esto no tiene mucha utilidad, ya que si queremos instanciar a crate::factura::crear_factura::producto el compilador nos dará error porque no podemos enviar valores a todos los miembros, pero en algunos casos es necesario para no alterar ciertos valores que solo el módulo tiene derecho a modificar:

// ...

mod factura {
    pub mod crear_factura {
        #[derive(Debug)]
        pub struct Producto {
            id: u64,
            pub nombre: String,
            pub precio: f32,
            tipo: String,
        }

        pub fn agregar_producto(nombre: String) -> Producto {
            let mi_producto = Producto {
                id: 123,
                nombre,
                precio: 280.99,
                tipo: String::from("TIPO_1"),
            };
            mi_producto
        }
        // ...
    }

    // ...
}

pub fn crear_zip() {
    // ...

    let p = factura::crear_factura::agregar_producto(String::from(""));
    println!("{:?}, {:?}", p.nombre, p.precio);
}

Creamos una función que se llama agregar_producto que solo recibe un nombre (esto solo es un ejemplo que no tiene mucho sentido, pero sígueme la corriente) y regresa un tipo Producto. Los otros valores del struct son asignados por la propia función. Luego, en crear_zip llamamos agregar_producto, que nos regresa la instancia de Producto y ya podemos acceder, pero solo a los miembros que hemos declarado como pub.

En el caso de los enums, si éste se hace público, todos sus miembros lo son, ya que no tendría mucho sentido solo usar algunas opciones, sobre todo si usamos pattern matching se puede complicar.

Keyword use en Rust

Hasta ahora, cada vez que hemos llamado a una función de un módulo hemos usado las notaciones absolutas o relativas. Sin embargo puede ser molesto estar llamando siempre así a todo lo que usemos. Rust tiene la keyword use para hacernos la vida más sencilla.

Si hacemos lo siguiente:

mod factura {
    pub mod crear_factura {
        pub fn sellar() {}
    }

    pub mod timbrar {
        fn timbrar_pac() {}
        fn sellar() {}
    }
}

use crate::factura::crear_factura;

pub fn facturar_ahora() {
    crear_factura::sellar();
}

Ahora el módulo crear_factura dentro de factura se puede usar directamente sin crear conflictos:

Pero si hacemos lo siguiente, veremos que hay un error:

mod factura {
    pub mod crear_factura {
        pub fn sellar() {}
    }

    pub mod timbrar {
        fn timbrar_pac() {}
        fn sellar() {}
    }
}

use crate::factura::crear_factura;

mod procesar_factura {
    pub fn facturar_ahora() {
        crear_factura::sellar();
    }
}

Esto es porque use crate::factura::crear_factura; está fuera del scope del módulo procesar_factura.

Para resolverlo solo agregamos esa línea dentro del módulo:

mod procesar_factura {
    // use super::factura::crear_factura;
    use crate::factura::crear_factura;

    pub fn facturar_ahora() {
        crear_factura::sellar();
    }
}

¿Y qué pasa si no me detengo en crate::factura::crear_factura? Es decir, si quiero usar directamente la función sellar. ¿Esto es posible? Claro, modifiquemos un poco:

mod procesar_factura {
    use super::factura::crear_factura::sellar;

    pub fn facturar_ahora() {
        sellar();
    }
}

Esto funciona bien y podemos usar ahora sellar sin llamar al crate padre.

Keyword as en Rust

Sin embargo, si revisamos el módulo, tenemos dentro de sus dos submódulos, tenemos la función con el mismo nombre: sellar. Si queremos hacer lo mismo que este último ejemplo, el compilador nos dirá que tenemos duplicada esa función.

Para resolver esto usamos la keyword as.

mod factura {
    pub mod crear_factura {
        pub fn sellar() {}
    }

    pub mod timbrar {
        fn timbrar_pac() {}
        pub fn sellar() {}
    }
}

mod procesar_factura {
    use super::factura::crear_factura::sellar;
    use super::factura::timbrar::sellar as sellar_timbre;

    pub fn facturar_ahora() {
        sellar();
        sellar_timbre();
    }
}

Tenemos la primera llamada, tal como está. Para la segunda vez que llamamos sellar, usamos as para renombrar esa función a sellar_timbre y posteriormente llamarla en facturar_ahora.

Separar módulos en archivos

Por ahora solo hemos visto módulos y código en un solo archivo. En la vida real esto no es así porque tendríamos archivos de miles de línea. Es mucho mejor tener organizado nuestro código en pedazos que podamos mantener de manera más sencilla.

Vamos a dividir el código en los siguientes archivos:

  • src/lib.rs
  • src/factura.rs

Recordemos que estamos revisando en este ejemplo un proyecto de librería, es decir, uno cuyo crate root es lib.rs.

Veamos el archivo src/lib.rs. Donde declaramos que usaremos el código de factura.rs mediante mod factura;:

// src/lib.rs

mod factura;

use factura::crear_factura;
use factura::timbrar;

fn facturar_ahora() {
    crear_factura::sellar();
    timbrar::sellar();
}

En el archivo src/factura.rs vamos a crear nuestros módulos crear_factura y timbrar ambos declarados como pub para que sea accedidos públicamente:

// src/factura.rs
pub mod crear_factura {
    pub fn sellar() {}
}

pub mod timbrar {
    fn timbrar_pac() {}
    pub fn sellar() {}
}

Sin embargo, también tenemos una manera mucho más específica de separar código, usando más archivos y una estructura más “verbosa”:

.
├── Cargo.lock
├── Cargo.toml
├── src
  ├── factura
     ├── crear_factura.rs
     └── timbrar.rs
  ├── factura.rs
  └── lib.rs

Para nuestro create root, usamos el mismo código:

// src/lib.rs
mod factura;

use factura::crear_factura;
use factura::timbrar;

fn facturar_ahora() {
    crear_factura::sellar();
    timbrar::sellar();
}

Ahora, en el archivo src/factura.rs llamamos a los dos módulos crear_factura y timbrar. Estos serán buscados por el compilador en el directorio factura que es el mismo nombre del archivo a nivel de src/lib.rs, además de ser los mismos nombres de los módulos:

// src/factura.rs
pub mod crear_factura;
pub mod timbrar;

El archivo src/factura/crear_factura.rs contiene las funciones solamente, sin incluir el nombre del módulo:

// src/factura/crear_factura.rs
pub fn sellar() {}

Para el otro es lo mismo: src/factura/crear_factura.rs contendrá las funciones necesarias sin incluir el nombre del módulo.

// src/factura/timbrar.rs
fn timbrar_pac() {}
pub fn sellar() {}

Con esto terminamos de explorar los Crates, paquetes y módulos en Rust. Separar código es fundamental es importante. Más info acá.

Gracias por leer.


Posted

in

, , ,

by