La estructura de un proyecto (3): la función que nadie podía probar
Por Dorian Chávez · fundador y CEO de Hábil ·
Toda tu lógica en main.rs y tus pruebas de tests/ no pueden importarla, ni con pub. Qué va en lib.rs, qué queda privado y una plantilla que lo comprueba.
La estructura de un proyecto · entrega 3 de 7 · Rust
Escenario: los personajes y la historia son inventados. Los datos técnicos y las citas llevan su fuente al final.
En 1 minuto
- En Rust, una prueba de
tests/es otro crate. Solo puede usar lo que una biblioteca declarapub. Si tu proyecto solo tienemain.rs, no hay nada que importar [1]. - La salida es partir el programa: la lógica en
src/lib.rsy unmain.rsdelgado que solo la llama [2][3]. - En Rust todo es privado mientras no digas
pub[3][5]. No es una molestia: es una frontera que el compilador vigila, y te deja cambiar lo de adentro sin romper a nadie. - Te llevas una plantilla que se clona y corre a la primera, con un guion que demuestra esa frontera poniéndose rojo cuando debe.
- Para el lunes: busca en tu proyecto qué funciones de
main.rsmerecen una prueba. Ésas van en la biblioteca.
En este artículo
- ¿Qué pasó el martes a las 19:20?
- ¿Qué pide el Rust Book?
- ¿Cuáles son cinco errores frecuentes?
- Ejercicio: ¿puedes cruzar la frontera?
- Para llevar
- ¿Qué sigue?
- La plantilla y el curso
- ¿Qué hace Hábil?
- Referencias
¿Qué pasó el martes a las 19:20?
Iván quiso probar una función desde fuera, y el compilador le contestó que no había nada que importar.
Martes, 19:20. Piso tres.
Tercer encargo, primero en Rust. El revisor otra vez, el de Go y Python, que consulta servicios por HTTP. El pedido: «Que el reporte no deje ver los tokens de las URL».
El programa lo había escrito alguien que ya no estaba. Todo vivía en un solo archivo:
src/main.rs 900 líneas
Cargo.toml
Cargo.lockIván encontró la función que armaba la URL del reporte, cerca de la línea 600, y la arregló. Quería una prueba que dijera «el token no sale», de las que viven en tests/. Creó tests/reporte.rs y escribió, como en el libro:
use revisor::url_publica;cargo test. La respuesta (recortada):
error[E0432]: unresolved import `revisor`
= help: if you wanted to use a crate named `revisor`, use `cargo add revisor` to add it to your `Cargo.toml`¿Un crate llamado revisor? Su proyecto se llamaba así. Le puso pub a la función y volvió a correr. El mismo error, palabra por palabra. Y la sugerencia apuntaba a un paquete de fuera; él quería el suyo.
A las 19:41 le escribió a Lucía, la líder técnica.
—Le puse pub y sigue sin encontrarla.
—¿Dónde está tu biblioteca? —contestó ella.
—No tengo. Es un programa.
—Ahí está. Un programa no exporta nada. Tus pruebas de tests/ son otro crate, y otro crate solo puede usar una biblioteca. Mañana partimos ese main.rs en dos.
Iván miró las 900 líneas. El compilador no le estaba diciendo que su código estuviera mal. Le estaba diciendo que no tenía puerta.
¿Qué pide el Rust Book?
Que la lógica viva en una biblioteca y que main.rs solo la llame, porque solo una biblioteca se puede importar desde fuera.
El capítulo de pruebas del Rust Book lo dice sin rodeos. Si un proyecto solo tiene src/main.rs, no puedes crear pruebas de integración en tests/ que importen sus funciones, porque «Only library crates expose functions that other crates can use; binary crates are meant to be run on their own» [1]: los binarios están hechos para ejecutarse solos.
Y da la salida en el mismo párrafo: un src/main.rs sencillo que llama a la lógica de src/lib.rs [1]. El capítulo 12 lo vuelve receta y a main le deja poco: leer los argumentos, preparar la configuración, llamar a la biblioteca y atender el error [2].
Medido en un proyecto de una función: en main.rs, privada o pub, la prueba da E0432; en lib.rs y pub, pasa.
La plantilla de esta entrega, estructura-rust, está hecha así:
estructura-rust/
├── Cargo.toml · Cargo.lock
├── servicios.yaml el ejemplo
├── src/
│ ├── main.rs EL PROGRAMA: opciones, llamada, código de salida
│ ├── lib.rs LA BIBLIOTECA: declara los módulos y qué es público
│ ├── servicio.rs el vocabulario. Sin red ni archivos
│ ├── config.rs lee y valida servicios.yaml
│ ├── revisar.rs consulta muchos servicios a la vez
│ └── reporte.rs tabla y JSON
├── tests/ cada archivo es un crate aparte
│ ├── integracion.rs
│ ├── binario.rs
│ └── comun/mod.rs
├── herramientas/probar-frontera.sh
├── .github/workflows/verificar.yml
└── README.md · LICENSE · LICENSE-CONTENIDO.mdVamos parte por parte, con la pregunta de siempre: ¿qué problema resuelve hoy?
¿Por qué hay dos raíces en src/?
lib.rs es la biblioteca y main.rs es el programa: dos crates del mismo paquete, y Cargo encuentra los dos sin que se lo pidas.
Son las rutas por convención de Cargo: la biblioteca en src/lib.rs, el ejecutable en src/main.rs y las pruebas de integración en tests/ [6]. Por eso Cargo.toml no declara ninguna.
Lo interesante es cómo se hablan. El programa es un usuario más de la biblioteca, como un crate externo: «It can only use the public API» [3]. main.rs tiene los mismos permisos que tests/ y que cualquier proyecto ajeno, y el libro lo ve como ventaja: al ser autor y cliente, diseñas mejor tu API [3].
¿Qué hace main.rs, entonces?
Lo mínimo que un programa no puede delegar: leer las opciones, llamar y decidir con qué código termina.
En la plantilla, main.rs lee tres opciones, llama a la biblioteca, imprime el reporte y sale con 0, 1 o 2. No decide qué es un servicio sano. Lo que queda se prueba ejecutándolo entero: cuando hay pruebas de integración, Cargo compila el programa antes y deja su ruta en CARGO_BIN_EXE_<nombre> [7]. Así trabaja tests/binario.rs. Lo de main.rs no se puede importar desde fuera; ejecutar, sí.
¿Qué significa que todo sea privado?
Que nada se ve fuera del módulo donde nace, salvo desde sus submódulos, mientras no le pongas pub; y el compilador lo hace cumplir.
El Rust Book lo dice con una lista: en Rust, todos los elementos (funciones, métodos, estructuras, enums, módulos y constantes) «are private to parent modules by default» [3]. La referencia del lenguaje lo resume: «By default, everything is private», con dos excepciones que no vienen al caso aquí [5].
¿Por qué así? Para que sepas qué partes puedes cambiar sin romper el código de fuera, dice el libro [3]. Y llama a la API pública de tu biblioteca «your contract with users of your crate» [3]: tu contrato con quien la usa.
Hay un punto medio, pub(crate): visible en todo el crate actual [5]. Engaña, porque una prueba de tests/ es otro crate (medido: da el mismo error que lo privado).
En la plantilla, reporte.rs ofrece dos funciones públicas, tabla y json. La que limpia la URL, url_publica, es privada: es un detalle de cómo se arma el reporte.
Entonces, ¿cómo se prueba url_publica?
Desde adentro: las pruebas unitarias viven en el mismo archivo y sí ven lo privado.
La convención del libro es un módulo tests al final de cada archivo de src/, marcado con #[cfg(test)] para que solo se compile con cargo test [1]. Es un submódulo hijo del archivo, y «items in child modules can use the items in their ancestor modules» [1]: un hijo ve lo privado de sus ancestros. Por eso «Rust’s privacy rules do allow you to test private functions» [1]. El reparto en la plantilla:
| Dónde | Qué ve | Qué prueba |
|---|---|---|
Al final de servicio.rs, config.rs y reporte.rs | Todo, lo privado incluido | Cada pieza sola: url_publica, clasificar, los errores de config |
tests/integracion.rs | Solo lo pub de la biblioteca | Las piezas juntas, contra un servidor HTTP local |
tests/binario.rs | El programa, como proceso | Lo que imprime y con qué código sale |
La documentación de servicio.rs | Lo pub | Que el ejemplo de clasificar siga siendo cierto |
Lo último merece una línea. El ejemplo que está en el comentario de clasificar también es una prueba: cargo test extrae los ejemplos de la documentación de la biblioteca y los ejecuta [8]. Los del programa, no: medido con un ejemplo falso a propósito, en main.rs nadie lo corre y todo sale verde; en lib.rs, el mismo ejemplo se pone rojo. Otra razón para que la lógica viva en lib.rs.
Así se ve cargo test --locked en la plantilla, solo las líneas que importan (las rutas, recortadas; los tiempos cambian):
Running unittests src/lib.rs (…)
test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (…)
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/binario.rs (…)
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s
Running tests/integracion.rs (…)
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.13s
Doc-tests revisor
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sSon 49 pruebas en cinco bloques. Se cuentan ahí, en las líneas test result, y no en el código de salida: si un archivo de tests/ no compila, su bloque ni siquiera aparece.
¿Por qué tests/comun/mod.rs y no tests/comun.rs?
Porque cada archivo de la raíz de tests/ es una suite, y uno de ayudas saldría como una suite vacía en cada corrida.
Las dos pruebas de integración comparten un servidor HTTP de prueba en 127.0.0.1. Si viviera en tests/comun.rs, Cargo lo compilaría como una suite más [1], y saldría en cada corrida con running 0 tests (medido). El libro muestra ese caso y dice que «is not what we wanted» [1]. Su salida es la de la plantilla: una subcarpeta, porque los archivos de las subcarpetas de tests/ no se compilan como crates aparte [1]. Cada prueba la trae con mod comun;.
¿Y los módulos de src/, sin mod.rs?
Un archivo por módulo, con el nombre del módulo. Es la ruta que el libro llama más idiomática.
Cada línea pub mod reporte; de lib.rs le dice al compilador que busque src/reporte.rs. El Rust Book enseña esa ruta y reconoce otra más vieja, src/reporte/mod.rs, que todavía funciona [4]. Cuál conviene, y qué pasa si se mezclan, va en el error 3.
¿Qué cuida revisar.rs que una plantilla no debe enseñar mal?
Un tiempo límite que cubre toda la consulta, un tope para lo que se lee y errores que nunca repiten la URL.
El tiempo límite abarca la consulta completa, del nombre del servidor al cuerpo, y del cuerpo se leen como mucho 64 KiB. Un matiz medido: si el servidor de nombres no contesta, el reporte sale a tiempo, pero el programa tarda en terminar lo que tarde el sistema en rendirse (en Linux, unos 10 s). Los errores de reqwest, la biblioteca HTTP, «may include the full URL», avisa su documentación [12]; la plantilla nunca usa su texto. Tampoco el de los errores de YAML, que repiten el valor (medido): dice el servicio, el campo y, si el YAML no se lee, la línea y la columna. Quitamos cada protección en una copia, una por una: las diez se pusieron rojas. Y los bordes (el código 400, --paralelo 0, 1 y 60,000 ms) tienen su prueba, que se pone roja si el borde se mueve.
¿Y dónde va el arreglo de Iván?
En la biblioteca, en reporte.rs, junto a quien la usa; y su prueba, al final del mismo archivo.
El miércoles, Iván y Lucía partieron el main.rs. La función de la URL quedó en reporte.rs, privada, con su prueba unitaria al lado. La prueba que Iván quería, «el token no sale», quedó en tests/, pero usa la función pública json. Desde fuera se prueba lo que se promete; desde adentro, cómo se cumple.
¿Cómo sé que la frontera existe?
Porque un guion la cruza a propósito en cada cambio y exige que el compilador lo rechace por la razón exacta.
herramientas/probar-frontera.sh trabaja en una copia temporal. Siembra tests/frontera.rs, una prueba que llama a revisor::reporte::url_publica, y la compila dos veces sobre la misma copia. Entre las dos solo cambia una palabra de src/reporte.rs, pub:
- Control, con
pub fn url_publica. La prueba tiene que compilar y pasar. Si no, el experimento está mal montado (la función cambió de nombre, la prueba sembrada tiene un error) y el guion se detiene en rojo. - Violación, con
src/reporte.rstal como está. La misma prueba tiene que no compilar, con el errorE0603, en la prueba y no en la biblioteca.
Ésta es la salida real:
1/2 control: con «pub fn url_publica», una prueba de tests/ la llama
✅ compiló y pasó (1 de 1): la prueba sembrada está bien escrita
2/2 violación: la misma prueba, con src/reporte.rs tal como está
error[E0603]: function `url_publica` is private
--> tests/frontera.rs:6:27
✅ el compilador la rechazó: lo que no es pub no se ve desde tests/
✅ frontera: se sostiene¿Por qué el control? Porque un experimento que solo espera un rojo se pone «verde» también cuando falla por la razón equivocada. Lo rompimos de cuatro maneras: con la función renombrada o la prueba sembrada mal escrita, se para en «el control NO pasó»; con otro código de error esperado, dice que falló «pero NO por la privacidad»; con la función pub, dice ❌ COMPILÓ. Con pub(crate), se sostiene.
El CI de GitHub está escrito para correr formato, clippy, las pruebas y la frontera con la Rust estable y con la 1.85.0, y la frontera también en macOS con el /bin/bash del sistema, en cada cambio y cada lunes. La plantilla ya está publicada en GitHub. Si pasa 60 días sin actividad, GitHub apaga la corrida de los lunes [20].
¿Cuáles son cinco errores frecuentes?
Los dos primeros salen de no saber qué ve tests/; el tercero, de mezclar estilos; los dos últimos, de abrir de más: la API o el proyecto.
1. Todo en main.rs, sin lib.rs
Funciona, pero desde tests/ no se puede importar nada, ni con pub.
Es el error de Iván [1]. El capítulo 12 da la razón de fondo: como no puedes probar la función main directamente, sacar la lógica de ella te deja probarla toda, y lo que queda se revisa leyéndolo [2]. Lo delata E0432, con una sugerencia de cargo add que apunta a otro lado (medido). No necesitas otro paquete: necesitas tu biblioteca.
2. Las ayudas de prueba en tests/common.rs
Cargo lo compila como una suite más, y sale vacía en cada corrida.
El libro lo muestra: tests/common.rs aparece con running 0 tests, aunque no tenga pruebas [1]. No rompe nada, pero ensucia el conteo. La salida es la subcarpeta, tests/common/mod.rs [1].
3. Mezclar mod.rs con archivos que llevan el nombre del módulo
Para el mismo módulo, el compilador se detiene; para módulos distintos, compila, pero confunde.
El Rust Book distingue dos casos. Si usas los dos estilos para el mismo módulo, «you’ll get a compiler error» [4]. Medido en la plantilla, con src/reporte.rs y src/reporte/mod.rs a la vez:
error[E0761]: file for module `reporte` found at both "src/reporte.rs" and "src/reporte/mod.rs"En módulos distintos compila, pero «might be confusing for people navigating your project» [4], y con mod.rs acabas con muchos archivos llamados igual abiertos en el editor [4]. Escoge un estilo; la plantilla usa el que el libro llama más idiomático.
4. Poner pub a todo porque el compilador se queja
Cada pub agranda lo que otros pueden usar, y el compilador deja de avisarte si sobra.
Ante un E0603, pregunta antes quién necesita esa función. Si es una prueba, va al final del archivo, como prueba unitaria [1]. Si es otro módulo del mismo crate, alcanza pub(crate) [5].
Si publicas la biblioteca o la usa otro paquete, el costo está escrito en la guía de compatibilidad de Cargo, que trata de las versiones nuevas de un paquete: «Major: renaming/moving/removing any public items» [11]. La plantilla no se publica (publish = false): lo pub solo lo usan main.rs y tests/, que se arreglan en el mismo cambio. Aun así, hay un costo medido: lo pub apaga el aviso de código muerto. Con pub(crate) y sin uso, el compilador dice `warning: function url_publica is never used; con pub, se calla. Por eso la frontera de la plantilla se pone roja si url_publica se vuelve pub`.
5. No versionar Cargo.lock cuando tu CI lo necesita, o armar un workspace antes de tiempo
El primero te deja sin el registro de con qué se probó; el segundo, con una estructura que todavía no tiene para qué.
La FAQ de Cargo dice que Cargo.lock registra el estado de una compilación que funcionó, para repetirla igual en otro momento y otra máquina, y que así el CI solo falla por cambios nuevos y no por factores externos [9]. cargo new lo versiona por omisión; la FAQ aclara que depende de lo que necesite tu paquete [9]. Entre sus usos pone uno que la plantilla hace con su fila 1.85.0 del CI: «Verifying a minimum-supported Rust version (MSRV) that is less than the latest version of a dependency supports» [9]. Y avisa del límite: ese determinismo «can give a false sense of security», porque Cargo.lock no afecta a quien usa tu paquete [9]. La plantilla corre con --locked, que falla si el archivo falta o si cargo quisiera cambiarlo [8]. Medido, sin Cargo.lock:
error: cannot create the lock file …/Cargo.lock because --locked was passed to prevent thisEl otro extremo es un workspace el primer día: un conjunto de paquetes que comparten Cargo.lock y carpeta de compilación [10], que el Rust Book presenta para cuando el proyecto ya creció y quieres partirlo en varios crates [10]. Con un paquete, no hay nada que compartir.
| Error | Lo que lo delata | Fuente |
|---|---|---|
1. Todo en main.rs | E0432: unresolved import desde tests/ | [1][2] |
2. Ayudas en tests/common.rs | Una suite con running 0 tests | [1] |
3. mod.rs y archivo con nombre | E0761: file for module … found at both | [4] |
4. pub a todo | Ningún aviso de never used; si publicas, cada retiro es un cambio mayor | [3][11] |
5. Sin Cargo.lock con un CI que lo necesita, o un workspace con un solo paquete | cannot create the lock file con --locked; una estructura que nada usa | [8][9][10] |
Ejercicio: ¿puedes cruzar la frontera?
Vas a intentar usar una función privada desde tests/, ver cuánto hay que abrir para lograrlo y ver cómo el guion te avisa que abriste de más.
Necesitas Rust 1.85 o más nuevo (cargo --version), instalado con rustup, más Git y bash; unos 20 minutos, más un par de minutos la primera vez, mientras se compilan las dependencias.
- En Linux Mint 22, no uses
apt install cargo: Mint 22 se basa en Ubuntu 24.04 [16], que trae Rust 1.75 [15], y la edición 2024 llegó con la 1.85 [18]. Medido con ese cargo: `featureedition2024is required, más una sugerencia de agregarcargo-featuresaCargo.toml. No la sigas: actualiza Rust. Si ya lo instalaste conapt, quítalo consudo apt remove cargo rustc; si no, el instalador derustupte avisa que ya hay un Rust en/usr/bin(medido). Luego corresudo apt install build-essential curly el comando derustup` [13][14]:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shSin build-essential, sale `error: linker cc not found` (medido). La lección 0 del curso de Rust lo cuenta paso a paso. - En macOS, xcode-select --install y luego el mismo comando de rustup [13]. - Si ya tienes rustup, rustup update stable. En Windows, usa WSL. - Al terminar, abre otra terminal y comprueba que cargo --version diga 1.85 o más: en la misma terminal, cargo todavía no se encuentra o sigue siendo el viejo (medido).
Paso 1. Clona y comprueba que todo está en verde.
git clone https://github.com/HabilMX/estructura-rust
cd estructura-rust
cargo test --locked
bash herramientas/probar-frontera.shLo que debes ver: cinco líneas test result: ok. que suman 49 pruebas y, al final del guion, ✅ frontera: se sostiene.
Paso 2. Predice antes de tocar. Vas a escribir una prueba en tests/ que llame a url_publica, que es privada. Anota tres cosas: ¿compila así? ¿Y si la vuelves pub(crate)? ¿Y si la vuelves pub?
Paso 3. Intenta cruzar.
cat > tests/mia.rs <<'EOF'
#[test]
fn quita_el_token() {
assert_eq!(
revisor::reporte::url_publica("https://pagos.ejemplo.com/salud?token=x"),
"https://pagos.ejemplo.com/salud"
);
}
EOF
cargo test --locked --test miaNo compila. La primera línea del error:
error[E0603]: function `url_publica` is privatePaso 4. Ábrela para el crate. Copia solo la línea de sed de tu sistema: el de macOS pide un argumento vacío después de -i (si en tu Mac sed es el de GNU, usa la de Linux).
# En Linux:
sed -i 's/^fn url_publica(/pub(crate) fn url_publica(/' src/reporte.rs
# En macOS:
sed -i '' 's/^fn url_publica(/pub(crate) fn url_publica(/' src/reporte.rs
cargo test --locked --test miaEl mismo E0603. pub(crate) abre la función para todo el crate de la biblioteca, y tu prueba es otro crate.
Paso 5. Ábrela del todo.
# En Linux:
sed -i 's/^pub(crate) fn url_publica(/pub fn url_publica(/' src/reporte.rs
# En macOS:
sed -i '' 's/^pub(crate) fn url_publica(/pub fn url_publica(/' src/reporte.rs
cargo test --locked --test mia
bash herramientas/probar-frontera.shTu prueba pasa (test result: ok. 1 passed). Pero el guion se pone rojo:
❌ COMPILÓ: una prueba de tests/ pudo llamar a revisor::reporte::url_publica. La frontera no existe.Lograste usarla desde fuera, y con eso la metiste a tu contrato. Ése es el control trabajando.
Paso 6. Regresa todo.
git checkout -- . && git clean -fd
bash herramientas/probar-frontera.shOjo: git clean -fd también borra los archivos nuevos que hayas creado ahí, como tus notas.
Cómo sabes que lo hiciste bien: corriste siete veces y viste tres desenlaces. Verde, cuatro veces: las dos del paso 1, tu prueba en el paso 5 y la frontera del paso 6. Rojo E0603, dos veces: los pasos 3 y 4. Y el rojo «COMPILÓ» del guion en el paso 5. Si en el paso 4 tu prueba compila, revisa que no hayas escrito pub en lugar de pub(crate). Si en el paso 5 sigue el E0603, la línea de sed no encontró pub(crate) fn url_publica(: revisa el paso 4.
Para ir más lejos (opcional): mueve tests/comun/mod.rs a tests/comun.rs (borra la carpeta vacía) y corre cargo test --locked. Antes, predice cuántos bloques test result vas a ver. Respuesta: seis, uno más que antes, Running tests/comun.rs con running 0 tests (medido). Regresa con el mismo git checkout -- . && git clean -fd.
Para llevar
Tres preguntas para hacerle a tu repositorio esta semana.
- ¿Tu proyecto tiene
src/lib.rs? Si no, ¿qué función demain.rste gustaría probar desde fuera y hoy no puedes? - De lo que es
puben tu biblioteca, ¿qué usa de verdad otro crate? Lo que nadie usa de fuera, ¿podría ser privado opub(crate)? - ¿Tu CI corre con
--locked? Si no, ¿con qué versiones de tus dependencias se probó el último verde?
¿Qué sigue?
En Go, la frontera la vigila la herramienta; en Python, la instalación; en Rust, el compilador. En TypeScript hay una que el editor acepta y que Node nunca ve.
Una semana después, Iván heredó una API en TypeScript. Un import con un alias bonito, @/revisar, compilaba sin una queja. El editor lo autocompletaba.
Node, al arrancar, no lo encontró.
¿Cómo puede compilar un programa que no arranca? El domingo 22 de noviembre, en la entrega 4: qué decide tsconfig.json, qué no decide, y por qué las pruebas y el código se compilan por separado.
La plantilla y el curso
- Plantilla: github.com/HabilMX/estructura-rust. Se usa como plantilla de GitHub o se clona [19]. El README explica cada carpeta, lo que no trae, cuándo agregar cada cosa y cómo quitar el ejemplo. El código es MIT; el texto y las explicaciones son CC BY-SA 4.0.
- Versiones: edición 2024 y
rust-version = "1.85", la primera con esa edición [18]. Se probó con Rust 1.99.1, la estable del 22 de octubre [17], en macOS arm64 (también con el/bin/bashdel sistema) y en Linux arm64 (contenedorubuntu:24.04), y con 1.85.0 en macOS arm64. Linux x86-64 todavía no se ha probado. El CI está escrito para la estable de cada semana y la 1.85.0 en Linux, y la estable en macOS [21]. Las dependencias van fijadas enCargo.lock. - Curso de Rust de Hábil: si los módulos,
pubo las pruebas de integración son nuevos para ti, la lección 6 los cuenta desde cero: Curso de Rust · Lección 6, Módulos, pruebas y Cargo.
Escribo desde lo que vemos todos los días en Hábil, donde construimos sistemas para banca, seguros y empresas en México. Si en tu trabajo o en tu escuela se topan con un repositorio que nadie sabe dónde tocar, platiquemos: habil.mx
¿Qué hace Hábil?
Enseñamos a programar con lo mismo que usamos para construir: código probado, explicado y abierto.
Esta serie nace del mismo lugar que nuestros cursos. El Curso de Rust es el segundo, después de Go: enseña de quién es cada dato y qué errores atrapa el compilador antes de ejecutar, y lo publicamos abierto, con licencia Creative Commons; su lección 6 cuenta los módulos, las pruebas y Cargo que usa la plantilla de este artículo. Y en el catálogo hay más cursos de programación, gratuitos y con código probado y explicado paso a paso.
Mira el Curso de Rust y el catálogo de Cursos de programación.
Si tu equipo hereda un sistema que nadie sabe dónde tocar, o necesita construir uno que se pueda seguir manteniendo, también lo hacemos: mira Construir su sistema a la medida. Pide tu primera conversación, sin costo. En ella revisamos si conviene configurar lo que ya tienen, usar un producto o construir, y qué recibirían en cada caso. Escríbenos por WhatsApp.
Fuentes consultadas el 22 de octubre de 2026. Las citas en inglés se reproducen tal cual.
Referencias
- Steve Klabnik, Carol Nichols y Chris Krycho, «Test Organization» (11.3), The Rust Programming Language. https://doc.rust-lang.org/book/ch11-03-test-organization.html
- Steve Klabnik, Carol Nichols y Chris Krycho, «Refactoring to Improve Modularity and Error Handling» (12.3): «Separation of Concerns for Binary Projects», The Rust Programming Language. https://doc.rust-lang.org/book/ch12-03-improving-error-handling-and-modularity.html
- Steve Klabnik, Carol Nichols y Chris Krycho, «Paths for Referring to an Item in the Module Tree» (7.3), incluida «Best Practices for Packages with a Binary and a Library», The Rust Programming Language. https://doc.rust-lang.org/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html
- Steve Klabnik, Carol Nichols y Chris Krycho, «Separating Modules into Different Files» (7.5): «Alternate File Paths», The Rust Programming Language. https://doc.rust-lang.org/book/ch07-05-separating-modules-into-different-files.html
- The Rust Reference, «Visibility and privacy». https://doc.rust-lang.org/reference/visibility-and-privacy.html
- The Cargo Book, «Package Layout». https://doc.rust-lang.org/cargo/guide/project-layout.html
- The Cargo Book, «Cargo Targets»: «Binaries» e «Integration tests». https://doc.rust-lang.org/cargo/reference/cargo-targets.html
- The Cargo Book, «cargo test»: «Documentation tests» y
--locked. https://doc.rust-lang.org/cargo/commands/cargo-test.html - The Cargo Book, «Frequently Asked Questions»: «Why have Cargo.lock in version control?». https://doc.rust-lang.org/cargo/faq.html
- Steve Klabnik, Carol Nichols y Chris Krycho, «Cargo Workspaces» (14.3), The Rust Programming Language. https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html
- The Cargo Book, «SemVer Compatibility». https://doc.rust-lang.org/cargo/reference/semver.html
- reqwest 0.13.5, «Struct Error», docs.rs. https://docs.rs/reqwest/0.13.5/reqwest/struct.Error.html
- Steve Klabnik, Carol Nichols y Chris Krycho, «Installation» (1.1), The Rust Programming Language. https://doc.rust-lang.org/book/ch01-01-installation.html
- The Rust Project, «Install Rust», rust-lang.org. https://www.rust-lang.org/tools/install
- Launchpad (Canonical), paquete
rustcpublicado en Ubuntu 24.04 «noble»: 1.75.0. https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=rustc&exact_match=true&distro_series=https://api.launchpad.net/1.0/ubuntu/noble - Linux Mint, «Linux Mint 22 Release Notes» («Linux Mint 22.x is based on Ubuntu 24.04»), linuxmint.com. https://www.linuxmint.com/rel_wilma.php
- The Rust Release Team, «Announcing Rust 1.99.1», Rust Blog, 15 de octubre de 2026. https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/
- The Rust Release Team, «Announcing Rust 1.85.0 and Rust 2024», Rust Blog, 20 de febrero de 2025. https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/
- GitHub Docs, «Creating a template repository». https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository
- GitHub Docs, «Disabling and enabling a workflow». https://docs.github.com/en/actions/how-tos/manage-workflow-runs/disable-and-enable-workflows
- David Tolnay, «dtolnay/rust-toolchain», README, GitHub. https://github.com/dtolnay/rust-toolchain
¿Su operación tiene estos desafíos?
¿Prefiere correo? Escríbanos a hola@habil.mx