Attributes

You have seen code like #[derive(Debug)] before: this type of code is called an attribute. These attributes are small pieces of code that give information to the compiler. They are not easy to create, but they are very easy to use. If you write an attribute with just # then it will affect the code on the next line. But if you write it with #! then it will affect everything in its own space.

Here are some attributes you will see a lot:

#[allow(dead_code)] and #[allow(unused_variables)]. If you write code that you don't use, Rust will still compile but it will let you know. For example, here is a struct with nothing in it and one variable. We don't use either of them.

struct JustAStruct {}

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

If you write this, Rust will remind you that you didn't use them:

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

We know that you can write a _ before the name to make the compiler be quiet:

struct _JustAStruct {}

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

but you can also use attributes. You'll notice in the message that it uses #[warn(unused_variables)] and #[warn(dead_code)]. In our code, JustAStruct is dead code, and some_char is an unused variable. The opposite of warn is allow, so we can write this and it will not say anything:

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

struct Struct1 {} // Create five structs
struct Struct2 {}
struct Struct3 {}
struct Struct4 {}
struct Struct5 {}

fn main() {
    let char1 = 'ん'; // and four variables. We don't use any of them but the compiler is quiet
    let char2 = ';';
    let some_str = "I'm just a regular &str";
    let some_vec = vec!["I", "am", "just", "a", "vec"];
}

Of course, dealing with dead code and unused variables is important. But sometimes you want the compiler to be quiet for a while. Or you might need to show some code or teach people Rust and don't want to confuse them with compiler messages.

#[derive(TraitName)] lets you derive some traits for structs and enums that you create. This works with many common traits that can be automatically derived. Some like Display can't be automatically derived, because for Display you have to choose how to display:

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

fn main() {
    let my_string = HoldsAString {
        the_string: "Here I am!".to_string(),
    };
}

The error message will tell you that.

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

But for traits that you can automatically derive, you can put in as many as you like. Let's give HoldsAString seven traits in a single line, just for fun, even though it only needs one.

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

fn main() {
    let my_string = HoldsAString {
        the_string: "Here I am!".to_string(),
    };
    println!("{:?}", my_string);
}

Also, you can make a struct Copy if (and only if) its fields are all Copy. HoldsAString has String which is not Copy so you can't use #[derive(Copy)] for it. But for this struct you can:

#[derive(Clone, Copy)] // You also need Clone to use Copy
struct NumberAndBool {
    number: i32, // i32 is Copy
    true_or_false: bool // bool is also Copy. So no problem
}

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); // If it didn't have copy, this would make an error
}

#[cfg()] means configuration and tells the compiler whether to run code or not. You see it usually like this: #[cfg(test)]. You use that when writing test functions so that it knows not to run them unless you are testing. Then you can have tests next to your code but the compiler won't run them unless you tell it to.

One other example using cfg is #[cfg(target_os = "windows")]. With that you can tell the compiler to only run the code on Windows, or Linux, or anything else.

#![no_std] is an interesting attribute that tells Rust not to bring in the standard library. That means you don't have Vec, String, and anything else in the standard library. You will see this in code for small devices that don't have much memory or space.

You can see many more attributes here.