Crates en Rust – Paquetes y módulos 1/2 – Aprende Rust 14/x

crates en rust

Los crates en Rust son como la base del código que el compilador empaqueta para ejecutarse y estos pueden contener otros crates, módulos y paquetes. Este post se dividirá en dos partes ya que se hará un poco largo.

Los crates en Rust puede ser de dos tipos:

  • Binary crate: estos tienen su función main y se pueden compilar y ejecutar, como un programa de línea de comandos o utilería.
  • Library crate: estos no se ejecutan tal como los binaries, sino que se incluyen en los programas para agregarle funcionalidad, por ejemplo las librerías que generan números aleatorios o aquellos que generan hashes como SHA256.

Hay un término llamado crate root. Este se refiere al directorio que contiene el archivo principal, que por lo general es src/main.rs y es el que el compilador busca para, a partir de él generar todo el ejecutable.

Paquetes en Rust(packages)

Un package son varios crates interactuando unos con otros, que proveen a nuestros programas un set de funcionalidades. Por ejemplo, imagina un package que importas para llamar a una URL de una API, traes la información de ella y luego la procesas como JSON. Bueno, pues para esto es posible que el package use tres o más crates para lograr eso en una sola función.

Cuando se trata de package, nuestro crate root será src/lib.rs.

Módulos en Rust

Declarar un módulo

Los crates en Rust permiten declarar nuevos módulos, sobre todo lo veremos en el crate root. Vamos a declarar un módulo garden haciendo mod garden;. El compilador lo buscará en estos lugares:

  • en las llaves siguientes a mod garden {}, reemplazando ;.
  • en el archivo src/garden.rs.
  • en el archivo src/garden/mod.rs.

Submódulos

Si declaras mod vegetables; en el archivo src/garden.rs, el compilador buscará desde el crate root, es decir:

  • en el mismo archivo, en las llaves siguientes a mod vegetables {}, reemplazando ;.
  • In the file src/garden/vegetables.rs.
  • In the file src/garden/vegetables/mod.rs.

Paths en módulos

Un path es el camino desde el crate root hasta la referencia que estamos invocando o vamos a usar, por ejemplo una función, un enum, etc.

Si creamos un type llamado Asparagus deberá ser accedido asi: crate::garden::vegetables::Asparagus.

Private vs public

El código de un módulo es private por default. Es decir, sus miembros no podrán ser accedidos por nadie (lo veremos adelante) y para hacerlos accesibles desde otro módulo usamos pub mod.

Vamos a ilustrar esto con un ejemplo en src/main.rs:

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {:?}!", plant);
}

La línea pub mod garden; incluye el código de src/garden.rs. Este código es:

pub mod vegetables;

En este archivo, solamente tiene esa línea e incluye a su vez el código de _src/garden/vegetables.rs, que es este:

#[derive(Debug)]
pub struct Asparagus {}

Ejemplo de una librería (package)

Vamos a estructurar el ejemplo de una librería que va a facturar cargo new factura --lib. Tenemos dos grandes módulos, uno referido al trabajo local, como agregar conceptos, calcular impuestos, etc. Y otra parte que es la encargada de comunicar con el proveedor de timbrado de facturas. Esto se ve algo así:

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

    mod timbrar {
        fn timbrar_pac_nombre() {}
        fn descarga_pdf() {}
        fn descarga_xml() {}
    }
}

Aquí vemos que podemos poner un módulo dentro de otro, en este caso dentro del módulo factura tenemos otros dos: crear_factura y timbrar. No solo se pueden declarar módulos, también podemos declarar structs, enums o funciones.

Así como dijimos que src/main.rs es el crate root, en este ejemplo, src/lib.rs es el crate root para nuestro package.

Esto es porque la ruta de acceso, que se llama module tree, empieza como una estructura donde la base es un crate:

  • crate
    • crear_factura
      • sellar
      • agregar_addenda
    • timbrar
      • timbrar_pac_nombre
      • descarga_pdf
      • descarga_xml

Paths en el Module Tree

Un path puede ser accedido de dos maneras:

  • Path absoluto: es la ruta completa, desde el crate root, es decir, el path inicia con la palabra crate.
  • Path relativo: este inicia desde el módulo en el que se está trabajando y usa self, super o el identificador del módulo.

NOTA: el siguiente código no funciona, pero servirá para ilustrar la estructura de los paths.

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

    mod timbrar {
        fn timbrar_pac() {}
        fn descarga_pdf() {}
        fn descarga_xml() {}
    }
}

pub fn crear_zip() {
    // Absoluto
    crate::factura::timbrar::descarga_pdf();
    // Relativo
    factura::timbrar::descarga_xml();
}

En la función crear_zip, cuando llamamos a descarga_pdf, usamos la palabra crate para comenzar a formar la ruta que nos llevará hasta la función. Cuando llamamos a descarga_xml usamos la forma relativa, es decir, empezamos invocando a la función desde factura::.

Si hacemos un cargo build. Nos tira errores advirtiendo que timbrar es privado. Por lo que no podemos llegar hasta las funciones que necesitamos. Estamos tratando de acceder de manera correcta, pero el compilador requiere que los módulos y miembros sea públicos para poder accederlos. También hay que ver que no necesitamos que factura sea pública, ya que crear_zip está al mismo nivel que ella.

Usando pub para acceder a los miembros

Podemos hacer público el módulo timbrar y con eso debería funcionar:

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

    pub mod timbrar {
        fn timbrar_pac() {}
        fn descarga_pdf() {}
        fn descarga_xml() {}
    }
}

pub fn crear_zip() {
    // Absolute path
    crate::factura::timbrar::descarga_pdf();
    // Relative path
    factura::timbrar::descarga_xml();
}

Pero NO 🤪

Esto es porque el modificador pub es granular. Es decir, esto solo hace público el módulo, pero sus miembros siguen siendo privados.

Entonces debemos agregar también pub a las dos funciones:

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

    pub mod timbrar {
        fn timbrar_pac() {}
        pub fn descarga_pdf() {}
        pub fn descarga_xml() {}
    }
}

pub fn crear_zip() {
    // Absolute path
    crate::factura::timbrar::descarga_pdf();
    // Relative path
    factura::timbrar::descarga_xml();
}

Ahora el código funciona (bueno, nos tira warnings porque no hemos usado algunas otras funciones).

Usando super para acceder a los paths

La keyword super se usa para acceder a miembros a los que el padre del módulo tiene acceso. Aclaremos esto:

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

    pub mod timbrar {
        fn timbrar_pac() {
            super::crear_factura::sellar();
            descarga_pdf();
        }
        pub fn descarga_pdf() {}
        pub fn descarga_xml() {}
    }
}

pub fn crear_zip() {
    // Absolute path
    crate::factura::timbrar::descarga_pdf();
    // Relative path
    factura::timbrar::descarga_xml();
}

Centrémonos en factura::timbrar::timbrar_pac.

Vemos que timbrar_pac puede tener acceso a descarga_pdf porque están al mismo nivel, es decir, los dos pertencen a timbrar, pero aunque su padre timbrar está al mismo nivel que el módulo crear_factura, ya no pueden llegar allá. Es cuando usamos a super.

Usamos super::crear_factura::sellar() para llegar hasta el módulo que su padre comparte, porque tanto timbrar como crear_factura son miembros de factura.

Podemos usar dos veces super. Un ejemplo:

fn user_login_pac() {}

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

    pub mod timbrar {
        fn timbrar_pac() {
            super::super::user_login_pac();

            super::crear_factura::sellar();

            descarga_pdf();
        }
        pub fn descarga_pdf() {}
        pub fn descarga_xml() {}
    }
}

// ...

Dentro de timbrar_pac usamos la siguiente línea super::super::user_login_pac(); para subir dos niveles y luego acceder a la función user_login_pac.

Por ahora aquí vamos a dejar el tema de los crates en Rust. Puedes consultar más info acá.

Gracias por leer.


Posted

in

, , ,

by