Cargo y módulos en Rust

El software va creciendo poco a poco en un proyecto y es vital que esté bien organizado. Rust incluye un potente sistema de módulos para manejar estas situaciones y Cargo nos ayuda a gestionar las dependencias del proyecto.

Módulos

Los módulos se definen con la palabra reservada mod y por defecto todo es privado. Es necesario indicar pub para hacer esos campos públicos. Con use podemos acortar la manera con la que hacemos referencia a los componentes del módulo. Así pues, use es similar a using namespace de C++.

mod network{
    pub fn version() -> String{
        String::from("Network 1.0.0")
    }
    pub mod server{
        fn private_thing(){

        }
        pub fn public_thing(){

        }
    }
}

fn main() {
    println!("{}",network::version());
    {
        use network::server::public_thing;
        public_thing();
    }
}

Los módulos resultan una opción más interesante si tenemos múltiples archivos. Por norma general, si un módulo se define en el fichero de entrada del compilador, este se busca en dos sitios:

  • Un fichero nombre_del_modulo.rs
  • Un fichero nombre_del_modulo/mod.rs (opción recomendada para módulos de varios archivos)

Por lo que el código anterior puede sustituirse por esto en el fichero principal:

mod network;

fn main() {
    println!("{}",network::version());
    {
        use network::server::public_thing;
        public_thing();
    }
}

Y esto en un fichero network.rs

pub fn version() -> String{
    String::from("Network 1.0.0")
}
pub mod server{
    fn private_thing(){

    }
    pub fn public_thing(){

    }
}

Podemos usar use-as para modificar el nombre con el que se importa una función.

mod network;

fn main() {
    println!("{}",network::version());
    {
        use network::server::public_thing as super_thing;
        super_thing();
    }
}

 

Crates

El código en Rust se organiza en crates, que son colecciones de módulos que se distribuyen. Piensa en ello como si fuesen gemas de Ruby o librerías de C++. Las crates se pueden compartir con la gente. El mayor repositorio de crates de la comunidad Rust es crates.io.

Las crates se pueden generar con rustc o con cargo, aunque es habitual usar la segunda opción. Vamos a ver primero como usaríamos una crate externa. En este caso voy a usar regex. Sé que en esta parte no vas a saber como descargar regex y usarla, pero voy a explicar el código.

extern crate regex;

use regex::Regex;

fn main() {
    let r = Regex::new(r"([A-Za-z0-9])([.]|[_]|[A-Za-z0-9])+@gmail.com").unwrap();
    if r.is_match("pucela_segunda@gmail.com") {
        println!("Correo válido de Gmail");
    }else{
        println!("Correo no válido de Gmail");
    }
}

Básicamente se usa extern crate para importar una crate externa, en este caso regex. Con use lo único que hacemos es acortar la manera de llamar a las funciones. Si no estuviese podríamos poner perfectamente regex::Regex::new en vez de Regex::new y funcionaría.

Cargo

Cargo es una herramienta que cumple dos funciones dentro del ecosistema Rust. Por un lado es un gestor de dependencias y por otro lado gestiona también las compilaciones.

En su parte de gestor de dependencias nos permite especificar que crates necesita el proyecto para compilar, su versión y si la crate lo soporta, con qué características activadas.

En su parte de gestor de compilaciones, realiza la compilación con rustc y añade las flags pertinentes al compilador. También se le pueden especificar cosas más complejas, aunque en Rust no suele ser habitual tener configuraciones de compilación excesivamente complejas.

Todo lo relativo a Cargo se define en el archivo Cargo.toml. Que tiene una estructura similar a esta:

[package]
name = "super_app"
version = "0.1.0"
authors = ["Adrián Arroyo Calle"]

[dependencies]
regex = "0.2.2"

En la sección dependencies puedes añadir línea por línea la crate que te haga falta. Consulta la documentación de cada crate para esto, pues puede haber variaciones.

Comandos básicos de Cargo

Crear un proyecto con Cargo

cargo new –bin mi_proyecto # si queremos que sea ejecutable

cargo new mi_crate # si queremos que sea una crate

Compilar y ejecutar

cargo run

cargo build # solo compilar

cargo build –release # compilar en modo Release

Cargo automáticamente se encarga de obtener las dependencias en la fase de compilación.

Instalar aplicaciones

cargo install APLICACION

Muchas herramientas de pueden distribuir a través de Cargo. Por ejemplo, Racer, un programa que sirve de autocompletado para IDEs como Eclipse o Visual Studio.

Ejecutar tests

cargo test

Generar documentación

cargo doc

Plugins de Cargo

Cargo es extensible gracias a plugins. Algunos interesantes son Clippy, cargo-audit, rustfmt,

loading...

Tutorial de Maud, motor de plantillas HTML para Rust

Seguimos aprendiendo en el blog sobre interesantes proyectos hechos para Rust. Ya hemos visto Iron, Piston y Neon. Hoy veremos Maud, un potente motor de plantillas que se centra en la eficiencia. Maud se compara a otras soluciones como Razor, ERB, Liquid,  Handlebars o Jade pero esta vez escribiremos nuestro HTML en Rust. ¿Locura? No, y de hecho funciona de forma bastante transparente. Vamos a verlo en acción

Comparativa de velocidad de diferentes motores. Maud es el más rápido (menos es mejor)

Instalando Maud

Maud usa plugins del compilador, una característica que a día de hoy no está activado ni en el canal estable ni el canal beta, solamente en el canal nightly. Para obtener una copia de Rust nightly lo ideal es usar Rustup.

Una vez hecho eso, creamos un nuevo proyecto y añadimos las dependencias de Maud al fichero Cargo.toml.

maud = "*"
maud_macros = "*"

Una simple plantilla

Ahora abrimos el archivo src/main.rs y vamos a empezar a usar Maud.

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
        let name = "Adrián";
        let markup = html!{
            p { "Hola, soy " (name) " y estoy usando Maud"}
        };
        println!("{}", markup.into_string());
}

La potencia de Maud se ve en la mega-macro html!. En esta macro escribiremos la plantilla que será compilada de forma nativa a Rust, lo que nos asegura una velocidad de ejecución excepcional. En este caso la salida será una etiqueta P de párrafo con la variable interpolada.

Simple, ¿no?

PreEscaped y otros elementos básicos

Por defecto en Maud todos el texto se convierte a HTML seguro. Es decir, no se pueden introducir etiquetas nuevas en el texto. Si por alguna razón necesitásemos añadir etiquetas nuevas podemos usar PreEscaped, que no realiza esta transformación de seguridad. Veamos el siguiente código:

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

use maud::PreEscaped;

fn main(){
        let name = "Adrián";
        let markup = html!{
                p { "Hola, soy " (name) " y estoy usando Maud" }
                p { "<h5>Esto no funcionará</h5>" }
                p { (PreEscaped("<h5>Esto sí funcionará</h5>")) }
        };
        println!("{}", markup.into_string());
}

El primer H5 se convertirá a código HTML seguro, es decir, no añadirá la etiqueta, en cambio se verá h5 en la web. Por contra con PreEscaped se añadirá la etiqueta h5 tal cual.

Los elementos son muy fáciles de añadir en Maud y por lo general no deberías usar PreEscaped salvo contadas ocasiones. Veamos como añadir más etiquetas.

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
        let name = "Adrián";
        let markup = html!{
                p { "Hola, soy " (name) " y estoy usando Maud" }
                p {
                        "Si la montaña no viene a Mahoma"
                        br /
                        "Mahoma va la montaña"
                        small em "Atribuido a Francis Bacon"
                }
        };
        println!("{}", markup.into_string());
}

En este ejemplo vemos como las etiquetas que no llevan texto como BR o INPUT debemos cerrarlas con una barra. Por otro lado es posible aglutinar varios niveles de etiquetas en una sola línea ( SMALL->EM->Texto).

Atributos, clases e IDs

En Maud es posible asignar atributos también, usando literales o variables. Para los atributos de texto la sintaxis es muy parecida a HTML.

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
        let name = "Adrián";
        let amazon = "http://www.amazon.com";
        let markup = html!{
                p { "Hola, soy " (name) " y estoy usando Maud" }
                p {
                        "Este texto contiene enlaces a "
                        a href="http://www.google.com" "Google"
                        " y a "
                        a href=(amazon) "Amazon"
                }
        };
        println!("{}", markup.into_string());
}

Además existen en HTML atributos vacíos. Es decir, atributos que con su sola presencia basta y normalmente no llevan valor asignado.

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
        let name = "Adrián";
        let allow_editing = true;
        let markup = html!{
                p { "Hola, soy " (name) " y estoy usando Maud" }
                p contenteditable?[allow_editing] {
                }
        };
        println!("{}", markup.into_string());
}

En este caso el atributo contenteditable se añade si la variable allow_editing es true. Si queremos añadir atributos vacíos en cualquier circunstancia podemos simplemente quitar [allow_editing] y dejar p contenteditable? {}.

Los IDs y las clases se añaden usando la sintaxis esperable, puntos y almohadillas.

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
        let name = "Adrián";
        let markup = html!{
                p { "Hola, soy " (name) " y estoy usando Maud" }
                p.red.font-big#editor contenteditable? {
                }
        };
        println!("{}", markup.into_string());
}

Estructuras de control

Maud soporta las estructuras de control básicas de Rust, if/else, if/let, for in y match.

#![feature(plugin)]
#![plugin(maud_macros)]

extern crate maud;

fn main(){
        let loggedIn = false;
        let email = Some("mariscal@example.com");
        let fonts = ["Arial","Times New Roman","Verdana"];
        let markup = html!{
                @if loggedIn {
                        h1 { "Has iniciado sesión" }
                } @else {
                        h1 { "Por favor, inicia sesión primero" }
                }

                @if let Some(mail) = email {
                        p { "Su email es " (mail) }
                }
                ol {
                        @for font in &fonts {
                                li (font)
                        }
                }

        };
        println!("{}", markup.into_string());
}

Como vemos, Maud posee suficientes características para ser interesante. Maud además permite extender el motor para representar cualquier tipo de dato. Por defecto Maud mirá si está implementado std::fmt::Display pero si queremos añadir etiquetas extra este método no funcionará. En cambio si se implementa la trait maud::Render tendrás control total sobre como va a mostrar Maud las variables de ese tipo. En la crate maud_extras se encuentran implementaciones por ejemplo de Markdown para Maud.

Maud se integra además con web frameworks de Rust, en especial con Iron y con Rocket. Sin duda, una de mis crates favoritas.

Usando Iron, un web framework para Rust


Quizá te interese echar un ojo al tutorial de Rocket, otro web framework para Rust

Rust cada día atrae a más desarrolladores. Es eficiente y es robusto. Mozilla ha sido la principal impulsora de este lenguaje para ser usado en entornos tan complejos como el propio Firefox.

Hoy vamos a introducirnos en el mundo del desarrollo web con Rust. Cuando la gente oye desarrollo web normalmente se piensa en lenguajes como PHP, Python, Ruby o JavaScript. Estos son lenguajes con los que es rápido desarrollar algo, aunque son mucho menos eficientes y es más fácil cometer errores debido a que son interpretados directamente. Un paso por encima tenemos a Java y C#, que cubren en parte las carencias de los otros lenguajes mencionados. Ha llegado la hora de hablar de Rust. Si bien es cierto que hay web frameworks en C++, nunca han sido muy populares. ¿Será Rust la opción que finalmente nos permita tener aplicaciones web con una eficiencia nativa?

Existen varios web frameworks en Rust, para este tutorial vamos a usar Iron, el más popular según Crates.io. Quizá te interese echar un vistazo también a Rocket, mi preferido.

ironframework

Crear proyecto e instalar Iron

Lo primero que hay que hacer es crear un nuevo proyecto en Rust, lo hacemos gracias a Cargo.

cargo new --bin MundoRust

cd MundoRust

Ahora editamos el fichero Cargo.toml para añadir las dependencias que vamos a usar.

[package]
name = "MundoRust"
version = "0.1.0"
authors = ["Adrián Arroyo Calle"]

[dependencies]
iron = "0.4.0"
router = "0.4.0"
staticfile = "0.3.1"
mount = "0.2.1"

Ahora obtenemos las dependencias especificadas con Cargo.

cargo run

Hola Mundo con Iron

Vamos a empezar a programar en Rust. Vamos a hacer una simple aplicación que devuelva “Hola Rustáceos” por HTTP.

Editamos el archivo src/main.rs

extern crate iron;

use iron::prelude::*;

fn hola(_: &mut Request) -> IronResult<Response> {
    Ok(Response::with((iron::status::Ok, "Hola Rustaceos")))
}

fn main() {
    Iron::new(hola).http("0.0.0.0:80").unwrap();
}

holarustaceos

Usando router para el enrutamiento

Hemos hecho un pequeño servidor HTTP con Iron. Pero nos falta algo, que sea capaz de manejar rutas. Que miweb.com/hola no sea lo mismo que miweb.com/adios. Iron por defecto no trae enrutador, pero es muy habitual usar Router, que ya hemos instalado antes por conveniencia.

extern crate iron;
extern crate router;

use iron::prelude::*;
use router::Router;

fn get_page(r: &mut Request) -> IronResult<Response>{
    let path = r.url.path();
    Ok(Response::with((iron::status::Ok, format!("Hola, peticion GET {}",path[0]))))
}

fn submit(_: &mut Request) -> IronResult<Response>{
    Ok(Response::with((iron::status::Ok, "Peticion POST")))
}

fn main() {
    let mut router = Router::new();

    router.get("/:page", get_page, "page");
    router.post("/submit", submit, "subdmit");

    Iron::new(router).http("0.0.0.0:80").unwrap();

}

getiron

Archivos estáticos

Para gestionar los ficheros estáticos vamos a usar staticfile y mount, otras dos librerías para Iron.

extern crate iron;
extern crate router;
extern crate staticfile;
extern crate mount;

use iron::prelude::*;
use router::Router;
use staticfile::Static;
use mount::Mount;

fn get_page(r: &mut Request) -> IronResult<Response>{
    let path = r.url.path();
    Ok(Response::with((iron::status::Ok, format!("Hola, peticion GET {}",path[0]))))
}

fn main() {
    let mut router = Router::new();

    router.get("/:page", get_page, "page");

    let mut mount = Mount::new();
    mount.mount("/public",Static::new("static/"));
    mount.mount("/",router);

    Iron::new(mount).http("0.0.0.0:80").unwrap();
}

ironstaticfile

 

Hemos dado nuestros primeros pasos en Iron, un web framework para Rust. Iron es completamente modular y soporta muchas más cosas que aquí no hemos visto. Gran parte de su funcionalidad se implementa a través de middleware, como en otros web frameworks populares.