Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents


Install Rust

Code Block
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh


Hello World Example


Code Block
themeEmacs
vi main.rs

...

Code Block
themeEmacs
./main
Code Block
Hello, world!


Using the Package Manager

Create a project using Cargo

Code Block
themeEmacs
cargo new hello_cargo

...

It has also initialized a new Git repository along with a .gitignore file. Git files won’t be generated if you run cargo new within an existing Git repository; you can override this behavior by using cargo new --vcs=git.


Cargo.toml

Code Block
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

[dependencies]                


Building

Code Block
themeEmacs
cargo build


Build and Run

Code Block
themeEmacs
cargo run

Build for Release 

Code Block
themeEmacs
cargo build --release

This command will create an executable in target/release instead of target/debug. The optimizations make your Rust code run faster, but turning them on lengthens the time it takes for your program to compile. This is why there are two different profiles: one for development, when you want to rebuild quickly and often, and another for building the final program you’ll give to a user that won’t be rebuilt repeatedly and that will run as fast as possible. If you’re benchmarking your code’s running time, be sure to run cargo build --release and benchmark with the executable in target/release.


Naming Conventions


ItemConvention
Cratessnake_case (but prefer single word)
Modulessnake_case
TypesCamelCase
TraitsCamelCase
Enum variantsCamelCase
Functionssnake_case
Methodssnake_case
General constructorsnew or with_more_details
Conversion constructorsfrom_some_other_type
Local variablessnake_case
Static variablesSCREAMING_SNAKE_CASE
Constant variablesSCREAMING_SNAKE_CASE
Type parametersconcise CamelCase, usually single uppercase letter: T
Lifetimesshort, lowercase: 'a


Variables

Immutable/Mutable

Variables by default are immutable, meaning they can't be changed once set.

Code Block
fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

The above code would generate an error since we are trying to overwrite the value of x.


We can make the variable mutable by adding mut the initial assignment:

Code Block
fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

The above code compiles.


Constants

You declare constants using the const keyword instead of the let keyword, and the type of the value must be annotated.

Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.

Code Block
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; 
const PI: f32 = 3.14;

Rust’s naming convention for constants is to use all uppercase with underscores between words.


Shadowing

You can declare a new variable with the same name as a previous variable. Rustaceans say that the first variable is shadowed by the second, which means that the second variable is what the compiler will see when you use the name of the variable.

Code Block
fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}
Code Block
themeEmacs
$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6


Data Types

Integer Types

LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize


Number Literals

Number literals can also use _ as a visual separator to make the number easier to read, such as 1_000, which will have the same value as if you had specified 1000.

Number literalsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'


Floating-Point Types

Rust’s floating-point types are f32 and f64, which are 32 bits and 64 bits in size, respectively. The default type is f64 because on modern CPUs, it’s roughly the same speed as f32 but is capable of more precision. All floating-point types are signed.


Code Block
fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}


Boolean Type

Booleans are one byte in size. The Boolean type in Rust is specified using bool.

Code Block
fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}


Character Type

Rust’s char type is the language’s most primitive alphabetic type.

Note that we specify char literals with single quotes, as opposed to string literals, which use double quotes.

Code Block
fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // with explicit type annotation
    let heart_eyed_cat = '😻';
}


Compound Types

Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

Tuple Type

A tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have a fixed length: once declared, they cannot grow or shrink in size.


Code Block
fn main() {
    let tup = (500, 6.4, 1);
    let (x, y, z) = tup;
    println!("The value of y is: {y}");
}

This program first creates a tuple and binds it to the variable tup. It then uses a pattern with let to take tup and turn it into three separate variables, x, y, and z. This is called destructuring because it breaks the single tuple into three parts. Finally, the program prints the value of y, which is 6.4.


We can also access a tuple element directly by using a period (.) followed by the index of the value we want to access. For example:

Code Block
fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);
    let five_hundred = x.0;
    let six_point_four = x.1;
    let one = x.2;
}


Array Type

Unlike a tuple, every element of an array must have the same type. Unlike arrays in some other languages, arrays in Rust have a fixed length.

Code Block
fn main() {
	let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];

	let a: [i32; 5] = [1, 2, 3, 4, 5];

	//access values

	let first = a[0];
	let second = a[1];
}


Functions

Rust code uses snake case as the conventional style for function and variable names, in which all letters are lowercase and underscores separate words

Code Block
fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}


Returning Values

Code Block
fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

The value returned from function plus_one is the value of x+1 or 6. Notice that there is no semi-colon at the end of the line. This is an expression.


Comments

Rust uses two forward slashes ( // ) to indicate a comment. 

For comments that extend beyond a single line, you’ll need to include // on each line.

Comments can also be placed at the end of lines containing code

Code Block
// So we’re doing something complicated here, long enough that we need
// multiple lines of comments to do it! Whew! Hopefully, this comment will
// explain what’s going on.

fn main() {
    let lucky_number = 7; // I’m feeling lucky today
}


Control Flow

If/Else

Code Block
fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}


Using if in a let Statement

Code Block
fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}


Loops

Code Block
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}


While

Code Block
fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}


For

Looping Through a Collection with for

Code Block
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}


Here’s what the countdown would look like using a for loop and another method we’ve not yet talked about, rev, to reverse the range:

Code Block
fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}


Iterating over a Range

Code Block
let mut sum = 0;
for i in 1..=5 {
    sum += i;
}

There are five kinds of ranges in Rust:

  • 1..5: A (half-open) range. It includes all numbers from 1 to 4. It doesn't include the last value, 5.
  • 1..=5: An inclusive range. It includes all numbers from 1 to 5. It includes the last value, 5.
  • 1..: An open-ended range. It includes all numbers from 1 to infinity (well, until the maximum value of the integer type).
  • ..5: A range that starts at the minimum value for the integer type and ends at 4. It doesn't include the last value, 5.
  • ..=5: A range that starts at the minimum value for the integer type and ends at 5. It includes the last value, 5.


Ownership

When you assign a variable to another or pass a variable to a function, the receiver takes ownership and also becomes responsible for dropping he memory when out of scope


See https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html


Example1

Code Block
fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.


Example2

Code Block
fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1

    let s2 = String::from("hello");     // s2 comes into scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}


References

We can send a variable by reference by pre-pending with an ampersand. This way, the ownership is not transfered.

Note: The opposite of referencing by using & is dereferencing, which is accomplished with the dereference operator, *. 


Code Block
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
  // it refers to, it is not dropped.


So, what happens if we try to modify something we’re borrowing? Spoiler alert: it doesn’t work!


Mutable Reference

We can allow the reference to be changed by adding the mutable (mut) keyword.

Code Block
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}


Mutable references have one big restriction: if you have a mutable reference to a value, you can have no other references to that value. 

The following code would generate an error:

Code Block
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);


Slices

String Slices

A string slice is a reference to part of a String, and it looks like this:

Code Block
let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];


With Rust’s .. range syntax, if you want to start at index 0, you can drop the value before the two periods.

By the same token, if your slice includes the last byte of the String, you can drop the trailing number. 

You can also drop both values to take a slice of the entire string.

Code Block
let s = String::from("hello world");

let hello = &s[..5];
let world = &s[6..];

let sentence = &s[..];
let sentence = &s[0..len];


Structs

 In a struct you’ll name each piece of data so it’s clear what the values mean. Adding these names means that structs are more flexible than tuples: you don’t have to rely on the order of the data to specify or access the values of an instance.


Code Block
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let mut user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    user1.email = String::from("anotheremail@example.com");
}


Creating Instances from Other Instances with Struct Update Syntax

Code Block
fn main() {
    // --snip--

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
}


The syntax .. specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.

Code Block
fn main() {
    // --snip--

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}


Defining Methods

Code Block
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}


Another Example

Code Block
pub struct Ticket {
    title: String,
    description: String,
    status: String,
}

impl Ticket {
    pub fn new(title: String, description: String, status: String) -> Ticket {
        if title.is_empty() {
            panic!("Title cannot be empty");
        }
        Ticket {
            title,
            description,
            status,
        }
    }

    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn description(&self) -> &String {
        &self.description
    }

    pub fn status(&self) -> &String {
        &self.status
    }
}




Static Methods

Code Block
struct Configuration {
    version: u32,
    active: bool
}

impl Configuration {
    // `default` is a static method on `Configuration`
    fn new() -> Configuration {
        Configuration { version: 0, active: false }
    }
}

// Call using following syntax:
let default_config = Configuration::new();


Modules

https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html


Modules Cheat Sheet

Before we get to the details of modules and paths, here we provide a quick reference on how modules, paths, the use keyword, and the pub keyword work in the compiler, and how most developers organize their code. We’ll be going through examples of each of these rules throughout this chapter, but this is a great place to refer to as a reminder of how modules work.

  • Start from the crate root: When compiling a crate, the compiler first looks in the crate root file (usually src/lib.rs for a library crate or src/main.rs for a binary crate) for code to compile.
  • Declaring modules: In the crate root file, you can declare new modules; say you declare a “garden” module with mod garden;. The compiler will look for the module’s code in these places:
    • Inline, within curly brackets that replace the semicolon following mod garden
    • In the file src/garden.rs
    • In the file src/garden/mod.rs
  • Declaring submodules: In any file other than the crate root, you can declare submodules. For example, you might declare mod vegetables; in src/garden.rs. The compiler will look for the submodule’s code within the directory named for the parent module in these places:
    • Inline, directly following mod vegetables, within curly brackets instead of the semicolon
    • In the file src/garden/vegetables.rs
    • In the file src/garden/vegetables/mod.rs
  • Paths to code in modules: Once a module is part of your crate, you can refer to code in that module from anywhere else in that same crate, as long as the privacy rules allow, using the path to the code. For example, an Asparagus type in the garden vegetables module would be found at crate::garden::vegetables::Asparagus.
  • Private vs. public: Code within a module is private from its parent modules by default. To make a module public, declare it with pub mod instead of mod. To make items within a public module public as well, use pub before their declarations.
  • The use keyword: Within a scope, the use keyword creates shortcuts to items to reduce repetition of long paths. In any scope that can refer tocrate::garden::vegetables::Asparagus, you can create a shortcut with use crate::garden::vegetables::Asparagus; and from then on you only need to write Asparagusto make use of that type in the scope.

Here, we create a binary crate named backyard that illustrates these rules. The crate’s directory, also named backyard, contains these files and directories:


Code Block
backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs


Code Block
use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {plant:?}!");
}


Visibility

When you start breaking down your code into multiple modules, you need to start thinking about visibility. Visibility determines which regions of your code (or other people's code) can access a given entity, be it a struct, a function, a field, etc.


By default, everything in Rust is private.
A private entity can only be accessed:

  1. within the same module where it's defined, or
  2. by one of its submodules


You can modify the default visibility of an entity using a visibility modifier.
Some common visibility modifiers are:

  • pub: makes the entity public, i.e. accessible from outside the module where it's defined, potentially from other crates.
  • pub(crate): makes the entity public within the same crate, but not outside of it.
  • pub(super): makes the entity public within the parent module.
  • pub(in path::to::module): makes the entity public within the specified module.

You can use these modifiers on modules, structs, functions, fields, etc. For example:

Code Block
pub struct Configuration {
    pub(crate) version: u32,
    active: bool,
}



Enums

Code Block
enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
Code Block
enum IpAddrKind {
	V4,
	V6,
}

struct IpAddr {
	kind: IpAddrKind,
	address: String,
}

let home = IpAddr {
	kind: IpAddrKind::V4,
	address: String::from("127.0.0.1"),
};

let loopback = IpAddr {
	kind: IpAddrKind::V6,
	address: String::from("::1"),
};


Match

A match statement that lets you compare a Rust value against a series of patterns.

There's one key detail here: match is exhaustive. You must handle all enum variants.
If you forget to handle a variant, Rust will stop you at compile-time with an error.


Code Block
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}


If you don't care about one or more variants, you can use the _ pattern as a catch-all:

Code Block
enum Status {
    ToDo,
    InProgress,
    Done
}

impl Status {
    fn is_done(&self) -> bool {
        match self {
            Status::Done => true,
			_ => false
        }
    }
}


Variants on Enums

Code Block
enum Status {
    ToDo,
    InProgress {
        assigned_to: String,
    },
    Done,
}

InProgress is now a struct-like variant.
The syntax mirrors, in fact, the one we used to define a struct—it's just "inlined" inside the enum, as a variant.


To access the variant, we need to use a match statement.

Code Block
match status {
    Status::InProgress { assigned_to } => {
        println!("Assigned to: {}", assigned_to);
    },
    Status::ToDo | Status::Done => {
        println!("Done");
    }
}


Concise Branching 

if/let

The if let construct allows you to match on a single variant of an enum, without having to handle all the other variants.

Code Block
impl Ticket {
    pub fn assigned_to(&self) -> &str {
        if let Status::InProgress { assigned_to } = &self.status {
            assigned_to
        } else {
            panic!(
                "Only `In-Progress` tickets can be assigned to someone"
            );
        }
    }
}


let/else

If the else branch is meant to return early (a panic counts as returning early!), you can use the let/else construct:

Code Block
impl Ticket {
    pub fn assigned_to(&self) -> &str {
        let Status::InProgress { assigned_to } = &self.status else {
            panic!(
                "Only `In-Progress` tickets can be assigned to someone"
            );
        };
        assigned_to
    }
}


Traits

Traits are Rust's way of defining interfaces. A trait defines a set of methods that a type must implement to satisfy the trait's contract.


Defining a trait

The syntax for a trait definition goes like this:

Code Block
trait <TraitName> {
    fn <method_name>(<parameters>) -> <return_type>;
}


We might, for example, define a trait named MaybeZero that requires its implementors to define an is_zero method:

Code Block
trait MaybeZero {
    fn is_zero(self) -> bool;
}


Implementing a trait

To implement a trait for a type we use the impl keyword, just like we do for regular1 methods, but the syntax is a bit different:

Code Block
impl <TraitName> for <TypeName> {
    fn <method_name>(<parameters>) -> <return_type> {
        // Method body
    }
}


For example, to implement the MaybeZero trait for a custom number type, WrappingU32:

Code Block
pub struct WrappingU32 {
    inner: u32,
}

impl MaybeZero for WrappingU32 {
    fn is_zero(self) -> bool {
        self.inner == 0
    }
}

Invoking a trait method


To invoke a trait method, we use the . operator, just like we do with regular methods:

Code Block
let x = WrappingU32 { inner: 5 };
assert!(!x.is_zero());


To invoke a trait method, two things must be true:

  • The type must implement the trait.
  • The trait must be in scope.

To satisfy the latter, you may have to add a use statement for the trait:

Code Block
use crate::MaybeZero;


Example of Multiple Implementation of a Trait

Code Block
trait Power<Exp> {
    fn power(&self, exp: Exp) -> Self;
}

impl Power<u32> for u32 {
    fn power(&self, exp: u32) -> u32 {
        self.pow(exp)
    }
}

impl Power<&u32> for u32 {
    fn power(&self, exp: &u32) -> u32 {
        self.pow(*exp)
    }
}

impl Power<u16> for u32 {
    fn power(&self, exp: u16) -> u32 {
        self.pow(exp as u32)
    }
}


Operator Overloading

In Rust, operators are traits.
For each operator, there is a corresponding trait that defines the behavior of that operator.


OperatorTrait
+Add
-Sub
*Mul
/Div
%Rem
== and !=PartialEq
<, >, <=, and >=PartialOrd

Arithmetic operators live in the std::ops module, while comparison ones live in the std::cmp module.


Code Block
struct WrappingU8 {
    inner: u8,
}

impl PartialEq for WrappingU8 {
    fn eq(&self, other: &WrappingU8) -> bool {
        self.inner == other.inner
    }
    
    // No `ne` implementation here
}



When you write x == y the compiler will look for an implementation of the PartialEq trait for the types of x and y and replace x == y with x.eq(y).

 It's syntactic sugar!



Derive macros

A derive macro is a particular flavour of Rust macro. It is specified as an attribute on top of a struct.

Code Block
#[derive(PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: String
}

Derive macros are used to automate the implementation of common (and "obvious") traits for custom types.

In the example above, the PartialEq trait is automatically implemented for Ticket.

 If you expand the macro, you'll see that the generated code is functionally equivalent to the one you wrote manually, although a bit more cumbersome to read:

Code Block
#[automatically_derived]
impl ::core::cmp::PartialEq for Ticket {
    #[inline]
    fn eq(&self, other: &Ticket) -> bool {
        self.title == other.title 
            && self.description == other.description
            && self.status == other.status
    }
}


Defer Trait

The Deref trait is the mechanism behind the language feature known as deref coercion.

By implementing Deref<Target = U> for a type T you're telling the compiler that &T and &U are somewhat interchangeable.
In particular, you get the following behavior:

  • References to T are implicitly converted into references to U (i.e. &T becomes &U)
  • You can call on &T all the methods defined on U that take &self as input.


String implements Deref with Target = str:

Code Block
impl Deref for String {
    type Target = str;
    
    fn deref(&self) -> &str {
        // [...]
    }
}


From and Into Trait

In std's documentation you can see which std types implement the From trait. You'll find that String implements From<&str> for String.

Thus, we can write:

Code Block
let title = String::from("A title");

// or 

let title:&str = "title";
let myString:String = String::from(title);

// or 

let title = "A title".into();


From and Into are dual traits.
In particular, Into is implemented for any type that implements From using a blanket implementation:


Code Block
impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}


Implementing From Trait Example:


Code Block
use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num);
}


Clone Trait

Its method, clone, takes a reference to self and returns a new owned instance of the same type.

Code Block
fn consumer(s: String) { /* */ }

fn example() {
     let mut s = String::from("hello");
     let t = s.clone();
     consumer(t);
     s.push_str(", world!"); // no error
}


To make a type Clone-able, we have to implement the Clone trait for it.
You almost always implement Clone by deriving it:

Code Block
#[derive(Clone)]
struct MyType {
    // fields
}


Copy Trait

If a type implements Copy, there's no need to call .clone() to create a new instance of the type: Rust does it implicitly for you.

Copy is not equivalent to "automatic cloning", although it implies it.
Types must meet a few requirements in order to be allowed to implement Copy.

First of all, it must implement Clone, since Copy is a subtrait of Clone. This makes sense: if Rust can create a new instance of a type implicitly, it should also be able to create a new instance explicitlyby calling .clone().

That's not all, though. A few more conditions must be met:

  1. The type doesn't manage any additional resources (e.g. heap memory, file handles, etc.) beyond the std::mem::size_of bytes that it occupies in memory.
  2. The type is not a mutable reference (&mut T).

If both conditions are met, then Rust can safely create a new instance of the type by performing a bitwise copy of the original instance—this is often referred to as a memcpy operation, after the C standard library function that performs the bitwise copy.


Implementing Copy

Code Block
#[derive(Copy, Clone)]
struct MyStruct {
    field: u32,
}


Drop Trait

The Drop trait is a mechanism for you to define additional cleanup logic for your types, beyond what the compiler does for you automatically.
Whatever you put in the drop method will be executed when the value goes out of scope.

Code Block
pub trait Drop {
    fn drop(&mut self);
}

If your type has an explicit Drop implementation, the compiler will assume that your type has additional resources attached to it and won't allow you to implement Copy.


Macros

You've already encountered a few macros in past exercises:

  • assert_eq! and assert!, in the test cases
  • println!, to print to the console

Rust macros are code generators.
They generate new Rust code based on the input you provide, and that generated code is then compiled alongside the rest of your program. Some macros are built into Rust's standard library, but you can also write your own. 


Generics

Generics allow us to write code that works with a type parameter instead of a concrete type:

Code Block
fn print_if_even<T>(n: T)
where
    T: IsEven + Debug
{
    if n.is_even() {
        println!("{n:?} is even");
    }
}

 

print_if_even is a generic function.
It isn't tied to a specific input type. Instead, it works with any type T that:


  • Implements the IsEven trait.
  • Implements the Debug trait.


This contract is expressed with a trait bound: T: IsEven + Debug.
The + symbol is used to require that T implements multiple traits. T: IsEven + Debug is equivalent to "where T implements IsEven and Debug".



If the trait bounds are simple, you can inline them directly next to the type parameter:

Code Block
fn print_if_even<T: IsEven + Debug>(n: T) {
    //           ^^^^^^^^^^^^^^^^^
    //           This is an inline trait bound
    // [...]
}


 Error Handling 

Option

Option is a Rust type that represents nullable values.
It is an enum, defined in Rust's standard library:

Code Block
enum Option<T> {
    Some(T),
    None,
}

Option encodes the idea that a value might be present (Some(T)) or absent (None).
It also forces you to explicitly handle both cases. You'll get a compiler error if you are working with a nullable value and you forget to handle the None case.
This is a significant improvement over "implicit" nullability in other languages, where you can forget to check for null and thus trigger a runtime error.


Code Block
pub fn assigned_to(&self) -> Option<&String> {

        if let Status::InProgress { assigned_to} = &self.status {
            Option::Some(assigned_to)
        } else {
            Option::None
        }
    }


Result

The Result type is an enum defined in the standard library:

Code Block
enum Result<T, E> {
    Ok(T),
    Err(E),
}

It has two variants:

  • Ok(T): represents a successful operation. It holds T, the output of the operation.
  • Err(E): represents a failed operation. It holds E, the error that occurred.

Both Ok and Err are generic, allowing you to specify your own types for the success and error cases.


Code Block
impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> Result<Ticket,String> {

        if title.is_empty() {
            return Result::Err("Title cannot be empty".into());
        }
        if title.len() > 50 {
            return Result::Err("Title cannot be longer than 50 bytes".into());
        }
        if description.is_empty() {
            return Result::Err("Description cannot be empty".into());
        }
        if description.len() > 500 {
            return Result::Err("Description cannot be longer than 500 bytes".into());
        }

        Result::Ok(
          Ticket {
            title,
            description,
            status,
          }
        )

    }
}


Processing the Result

When you call a function that returns a Result, you have two key options:


Panic if the operation failed. This is done using either the unwrap or expect methods

Code Block
// Panics if `parse_int` returns an `Err`.
let number = parse_int("42").unwrap();

// `expect` lets you specify a custom panic message.
let number = parse_int("42").expect("Failed to parse integer");


Destructure the Result using a match expression to deal with the error case explicitly

Code Block
match parse_int("42") {
    Ok(number) => println!("Parsed number: {}", number),
    Err(err) => eprintln!("Error: {}", err),
}


Code Block
let result = Ticket::new(title,d,status);

match result {
  Ok(ticket) => ticket,
  Err(err) => { panic!("{}",err); }
}



References