English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Gestión de organización en Rust

Cualquier lenguaje de programación que no pueda organizar el código es difícil de profundizar, prácticamente ningún producto de software se compila a partir de un solo archivo de origen.

Hasta ahora, todos los programas de este tutorial se han escrito en un solo archivo, principalmente para facilitar el aprendizaje de la sintaxis y conceptos del lenguaje Rust.

Para un proyecto, la organización del código es muy importante.

En Rust hay tres conceptos organizativos importantes: caja, paquete, módulo.

Caja (Crate)

La "caja" es un archivo de programa binario o archivo de biblioteca, que existe dentro del "paquete".

La "caja" es una estructura en forma de árbol, la raíz del cual es el programa compilado desde el archivo de origen que se compila cuando se inicia el compilador.

Nota: los "archivos de programas binarios" no necesariamente son "archivos ejecutables binarios", solo se puede determinar que contienen archivos con lenguaje de máquina de la máquina de destino, el formato del archivo varía según el entorno de compilación.

Paquete (Package)

Cuando ejecutamos el comando new de Cargo para crear un proyecto Rust, se creará un archivo Cargo.toml en el directorio del proyecto. La esencia del proyecto es un paquete, que debe ser gestionado por un archivo Cargo.toml, que describe la información básica del paquete y las dependencias.

Un paquete puede contener como máximo un contenedor de biblioteca "caja", puede contener cualquier cantidad de contenedores binarios, pero debe contener al menos un "caja" (ya sea una biblioteca o un contenedor binario).

Después de crear un paquete con el comando cargo new, se generará un archivo de origen main.rs en el directorio src, Cargo lo configura por defecto como la raíz del contenedor binario, el contenedor binario compilado después será el mismo que el nombre del paquete.

Módulo (Module)

Para una ingeniería de software, generalmente organizamos según las especificaciones de organización del lenguaje de programación utilizado, la estructura principal de la organización del módulo es generalmente un árbol. La unidad principal de organización de los módulos en Java es la clase, mientras que la forma principal de organización de los módulos en JavaScript es la función.

Las unidades organizativas avanzadas de este lenguaje pueden contenerse en capas, al igual que la estructura de directorio del sistema de archivos. La unidad organizativa en Rust es el módulo (Module).

mod nation {
    mod government {
        fn govern() {}
    }
    mod congress {
        fn legislate() {}
    }
    mod court {
        fn judicial() {}
    }
}

Este es un fragmento que describe el procedimiento de un estado de derecho: la nación (nación) incluye el gobierno (gobierno), el congreso (congreso) y el tribunal (tribunal), que tienen las funciones administrativas, legislativas y judiciales respectivamente. Se puede convertir en una estructura de árbol:

nación
 ├── government
 │ └── govern
 ├── congress
 │ └── legislate
 └── court
   └── judicial

En el sistema de archivos, la estructura de directorio se representa generalmente con el carácter diagonal (/) en la cadena de ruta, el separador de rutas en Rust es ::.

Las rutas se dividen en rutas absolutas y rutas relativas. La ruta absoluta comienza con la palabra clave crate. La ruta relativa comienza con las palabras clave self o super o un identificador. Por ejemplo:

crate::nation::government::govern();

Es la ruta absoluta de la función govern, la ruta relativa se puede representar como:

nation::government::govern();

Ahora puedes intentar definir una estructura de módulo similar en un programa de fuente y usar la ruta en la función principal.

Si haces esto, definitivamente encontrarás lugares incorrectos: el módulo government y sus funciones son privados (private), no se te permite acceder a ellos.

Permisos de acceso

En Rust hay dos tipos simples de permisos de acceso: público (public) y privado (private).

Por defecto, si no se agrega un modificador, el acceso a los miembros del módulo será privado.

Si se desea usar el permiso público, se debe usar la palabra clave pub.

Para los módulos privados, solo se puede acceder en posiciones de nivel igual o inferior, no se puede acceder desde el exterior.

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    mod congress {
        pub fn legislate() {}
    }
    
    mod court {
        fn judicial() {
            super::congress::legislate();
        }
    }
}
fn main() {
    nation::government::govern();
}

Este programa es compilable. Por favor, observe el método de acceso a super en el módulo court.

Si el módulo define una estructura, la estructura además de ser privada por sí misma, sus campos también son privados por defecto. Por lo tanto, si se desea usar la estructura y sus campos del módulo, se necesita declarar pub:

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }
    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}
pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    println!("Quisiera {} tostado, por favor", meal.toast);
}
fn main() {
    eat_at_restaurant()
}

Resultado de la ejecución:

Quisiera pan tostado de trigo, por favor

Las clases de enumeración pueden contener campos, pero no tienen propiedades similares:

mod SomeModule {
    pub enum Person {
        King {
            name: String
        },
        Quene
    }
}
fn main() {
    let person = SomeModule::Person::King {
        name: String::from("Blue")
    };
    match person {
        SomeModule::Person::King {name} => {
            println!("{}", name);
        }
        _ => {}
    }
}

Resultado de la ejecución:

Blue

Módulo difícil de encontrar

Los desarrolladores que han utilizado Java a menudo odian el bloque de clase más externo durante la programación - su nombre es idéntico al nombre del archivo, porque representa el contenedor del archivo, aunque es繁琐, no tenemos más remedio que escribirlo una vez para enfatizar "esta clase es la clase contenida en el archivo".

Sin embargo, esto tiene algunas ventajas: por lo menos hace que el desarrollador sea consciente de la existencia de la encapsulación de clase, y puede describir claramente la relación de herencia de la clase.

En Rust, los módulos son como las clases encapsuladas en Java, pero se puede escribir una función principal al principio del archivo, ¿cómo se explica esto?

Cada contenido de un archivo Rust es un módulo "difícil de encontrar".

Vamos a usar dos archivos para demostrar esto:

main.rs archivo

// main.rs
mod second_module;
fn main() {
    println!("Esto es el módulo principal.");
    println!("{}", second_module::message());
}

second_module.rs archivo

// second_module.rs
pub fn message() -> String {
    String::from("Esto es el 2nd module.")
}

Resultado de la ejecución:

Esto es el módulo principal.
Esto es el 2nd module.

El keyword use

El uso del keyword puede introducir el identificador del módulo en el ámbito actual:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
}
use crate::nation::government::govern;
fn main() {
    govern();
}

Este programa puede compilarse sin problemas.

Porque la palabra clave use importa el identificador govern al módulo actual, se puede usar directamente.

De esta manera, se resuelve el problema de las rutas de módulos locales largas.

Por supuesto, en algunos casos, hay dos nombres idénticos y se necesitan importar, podemos usar la palabra clave as para agregar alias a los identificadores:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub fn govern() {}
}
    
use crate::nation::government::govern;
use crate::nation::govern as nation_govern;
fn main() {
    nation_govern();
    govern();
}

Aquí hay dos funciones govern, una en nation y otra en government, usamos as para obtener el alias nation_govern de la función en nation. Los dos nombres pueden usarse al mismo tiempo.

La palabra clave use se puede usar junto con la palabra clave pub:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub use government::govern;
}
fn main() {
    nation::govern();
}

Referencia a la biblioteca estándar

Diccionario de bibliotecas estándar oficiales de Rust:https://doc.rust-lang.org/stable/std/all.html

Después de aprender los conceptos de este capítulo, podemos importar fácilmente las bibliotecas del sistema para desarrollar programas cómodamente:

use std::f64::consts::PI;
fn main() {
    println!("{}", (PI / 2.0).sin());
}

Resultado de la ejecución:

1

Todos los módulos de bibliotecas del sistema se importan por defecto, por lo que al usarlos, solo se necesita usar la palabra clave use para simplificar la ruta para usarlos cómodamente.