Crates & dependencies

The programming industry is built on collaboration. It's one of the very few industries where people freely share code, insights, learnings and resources for others to learn and build upon.

One way of sharing code is through packages or libraries, that can be integrated and used from another codebase. Rust has the concept of "crates", which are essentially pieces of code other people wrote that you can use and leverage in your own programs. You can think of crates like flexible building blocks.

Adding an external dependency

To add a dependency to your Rust project, you need to modify the Cargo.toml file, which is the configuration file for your project managed by Cargo, Rust's package manager. You can find example of crates under crates.io or running cargo search serde in your terminal.

Here's an example of how to add a dependency to your Cargo.toml file:

[dependencies]
serde = "1.0.160"

After editing that, run cargo build and check the output out.

Cargo Build

Using a dependency in your code

Now that a dependency has been added, we can call the functionality it provides within our code. To do so, we use the keyword use.

// Import the serde crate
use serde::{Serialize, Deserialize};

fn main() {
    // Rest of the code
    print!("Hello Serde!");
}

We're going to explore this in a little bit.

Crate features

Crate features are a way to enable or disable optional functionality in a Rust crate and allow users to choose which parts of a crate they need. They're similar to feature flags. In the previous example, along with installing serde, we can add methods that for example add functionality to derive (~ infer) things from struct definitions.

Update the Cargo.toml file with the following:

[dependencies]
serde = { version = "1.0.160", features = ["derive"] }
serde_json = "1.0" 

and then update the main.rs file:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Animal {
    name: String,
    age: i32,
}

fn main() {
    let dog = Animal {
        name: String::from("Buddy"),
        age: 3,
    };

    // This gives us a JSON string
    let serialized = serde_json::to_string(&dog).unwrap();
    println!("serialized struct = {}", serialized);

    // This gives us a Rust struct
    let deserialized: Animal = serde_json::from_str(&serialized).unwrap();
    println!("deserialized json = {:?}", deserialized);
}

Running this code in your IDE will output:

serialized struct = {"name":"Buddy","age":3}
deserialized json = Animal { name: "Buddy", age: 3 }

We'll dive a bit deeper into this later on as it can be extremely powerful.