Atributos

Ya se ha visto anteriormente código como este #[derive(Debug)]. Este tipo de código es un atributo. Los atributos son pequeñas piezas de código que dan información al compilador. No son fáciles de crear, pero son muy fáciles de usar. Un atributo puede comenzar con solo #, lo que significa que solo afecta al código de la siguiente línea. Sin embargo, si comienza con #! afectará a todo lo que esté en su espacio.

Hay atributos que aparecen mucho:

#[allow(dead_code)] y #[allow(unused_variables)]. Si el fichero contiene código que no se utiliza, Rust compilará, pero avisará. Por ejemplo, el siguiente código contiene un struct vacío y una variable. Ninguno se usa en el código:

struct JustAStruct {}

fn main() {
    let some_char = 'ん';
}

Rust se queja e indica que no se usan:

warning: unused variable: `some_char`
 --> src\main.rs:4:9
  |
4 |     let some_char = 'ん';
  |         ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_some_char`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: struct is never constructed: `JustAStruct`
 --> src\main.rs:1:8
  |
1 | struct JustAStruct {}
  |        ^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

Se ha visto que se puede utilizar un guión bajo para indicar que no se usa _:

struct _JustAStruct {}

fn main() {
    let _some_char = 'ん';
}

Pero también se puede utilizar un atributo. El propio mensaje de Rust indica que tiene activo los atributos #[warn(unused_variables)] y #[warn(dead_code)]. En el código anterior, JustAStruct es código muerto y some_char es una variable sin usar. El atributo opuesto a warn es allow, por lo que se puede escribire en el código y Rust dejará de avisar para estos casos:

#![allow(dead_code)]
#![allow(unused_variables)]

struct Struct1 {} // Crea cinco structs
struct Struct2 {}
struct Struct3 {}
struct Struct4 {}
struct Struct5 {}

fn main() {
    let char1 = 'ん'; // y cuatro variables. No se usa ninguno de ellos, pero el compilador ya no da ningún mensaje
    let char2 = ';';
    let some_str = "I'm just a regular &str";
    let some_vec = vec!["I", "am", "just", "a", "vec"];
}

Es importante tener en cuenta el código muerto y las variables sin uso, pero en ocasiones puede ser necesario que el compilador deje de avisar durante un tiempo. Se puede necesitar desarrollar el código o enseñar a alguien y no se quiere confundir con excesivos mensajes.

El atributo #[derive(NombreDeRasfgo)] permite derivar algunos rasgos para los struct y enum que se creen. Diversos rasgos de uso común, como Debug, pueden derivarse de esta forma. Otros, como Display, no se pueden derivar. En el caso de Display es necesari que se dedica por parte del desarrollador cómo se quiere mostrar el elemento.

// ⚠️
#[derive(Display)]
struct HoldsAString {
    the_string: String,
}

fn main() {
    let my_string = HoldsAString {
        the_string: "¡Aquí estoy!".to_string(),
    };
}

El mensaje de error lo indica bien claro:

error: cannot find derive macro `Display` in this scope
 --> src/main.rs:2:10
  |
2 | #[derive(Display)]
  |          ^^^^^^^

Pero en los casos de rasgos que se pueden derivar, se pueden indicar tantos como se necesite. En el siguiente ejemplo se le dan siete rasgos diferentes a un struct, solo por gusto, aunque solo se necesite uno en este caso.

#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
struct HoldsAString {
    the_string: String,
}

fn main() {
    let my_string = HoldsAString {
        the_string: "¡Aquí estoy!".to_string(),
    };
    println!("{:?}", my_string);
}

Se puede derivar Copy, pero solo en los casos que un elemento solo contiene elementos que también tienen el rasgo Copy. En el ejemplo anterior no es posible puesto que String no tiene dicho rasgo y el struct HodsAString contiene una variable de dicho tipo. Sin embargo en el siguiente ejemplo sí se puede derivar Copy al contener tipos que disponen de dicho rasgo:

#[derive(Clone, Copy)] // Se necesita Clone para usar Copy
struct NumberAndBool {
    number: i32, // i32 es Copy
    true_or_false: bool // bool es también Copy. Por lo que no es problema
}

fn does_nothing(input: NumberAndBool) {

}

fn main() {
    let number_and_bool = NumberAndBool {
        number: 8,
        true_or_false: true
    };

    does_nothing(number_and_bool);
    does_nothing(number_and_bool); // Sin Copy, esta fila daría error
}

El atributo #[cfg()] significa configuración e indica al compilador si ejecutar código o no. Normalmente, se encuentra de la siguiente forma #[cfg(test)]. Se usa cuando se escriben funciones de prueba para que el compilador solo las ejecute cuando se está probando. Así, el código de prueba puede estar junto al código del programa sin que el compilador lo ejecute, salvo cuando se le indica.

Otro ejemplo del uso de cfg es #[cfg(target_os = "windows")]. Que indica que solo se ejecute el código en windows (o linux u otro sistema).

El atributo #![no_std] indica a Rust que no incorpore la librería estándar. Esto implica que no se dispone de Vec, String y todo lo que aporta esta librería. Es útil cuando no es necesaria y el código tiene que ejecutarse en pequeños dispositivos con poca memoria.

Los diferentes atributos disponibles se pueden consultar aquí.