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.
Crates en Rust
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
- crear_factura
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.