Rust Programming Complete Guide Part 1

Rust Programming Complete Guide Part 1

Getting Started

Laying the groundwork for beginning with Rust key points here:

Installation of Rust:

  • Linux and macOS: Rust can be installed using the rustup tool via the terminal command:
    $ curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh
  • Windows: Rust installation involves using the installer available on the Rust website and ensuring that the MSVC build tools for Visual Studio are installed.

Writing a Basic Program:

  • The chapter guides through writing a “Hello, world!” program, explaining the structure of a basic Rust program.
  • It covers creating a project directory, writing the code, compiling, and running the program.

Using Cargo:

  • Cargo: The chapter introduces Cargo, Rust’s package manager and build system, explaining its benefits over using rustc directly.
  • Creating a Project: Steps to create a new project using Cargo:
    $ cargo new hello_cargo $ cd hello_cargo
  • Building and Running: Commands to build and run a Cargo project:
    $ cargo build $ cargo run

Project Structure:

  • Explanation of the project structure created by Cargo, including the src directory for source files and Cargo.toml for configuration.

Compiling and Running Programs:

Differentiating between cargo build, cargo run, and cargo check:

  • cargo build compiles the code.
  • cargo run compiles and runs the code.
  • cargo check checks the code for errors without producing a binary.

Common Programming Concepts in Rust

Variables and Mutability:

  • Immutable Variables:
let x = 5;
println!("The value of x is: {}", x);
x = 6; // This line would cause a compile-time error
  • Mutable Variables:
let mut y = 5;
println!("The value of y is: {}", y);
y = 6;
println!("The new value of y is: {}", y);

Constants:

   const MAX_POINTS: u32 = 100_000;
   println!("The maximum points are: {}", MAX_POINTS);
  1. Shadowing:
   let z = 5;
   let z = z + 1;
   let z = z * 2;
   println!("The value of z is: {}", z); // z is 12

Data Types:

  • Scalar Types:
let a: i32 = 10; //Integer
let b: f64 = 3.14; // Floating-point
let c: bool = true; // Boolean
let d: char = 'R'; // Character
  • Compound Types:
    • Tuple:
let tup: (i32, f64, u8) = (500, 6.4, 1); 
let (x, y, z) = tup; // Destructuring 
println!("The value of y is: {}", y); // 6.4
  • Array:
let arr = [1, 2, 3, 4, 5]; 
let first = arr[0]; 
println!("The first element is: {}", first);

Functions:

  • Defining Functions:
fn main() { another_function(5); } 
fn another_function(x: i32) { println!("The value of x is: {}", x); }
  • Parameters and Return Values:
fn five() -> i32 { 5 }
fn main() { 
let x = five(); println!("The value of x is: {}", x); // 5 }

Comments:

  • Single-line Comments:
    // This is a single-line comment
  • Multi-line Comments:
    rust /* This is a multi-line comment that spans multiple lines */ let y = 6;

Control Flow:

  • if Expressions:
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 { println!("Number is not divisible by 4 or 3"); }

Repetition with Loops:

  • Infinite Loops:
loop { 
println!("This will loop forever!"); 
break; // Add a break to prevent infinite looping 
}
  • Conditional Loops:
let mut number = 3; 
while number != 0 { println!("{}!", number); number -= 1; } 
println!("LIFTOFF!!!");
  • For Loops:
let arr = [10, 20, 30, 40, 50]; 
for element in arr.iter() { println!("The value is: {}", element); }

Understanding Ownership

Rust’s ownership system, a fundamental feature that ensures memory safety and efficient memory management without a garbage collector. Here’s a summary of the key concepts:

Ownership Basics:

  • In Rust, each value has a unique owner.
  • A value can have only one owner at a time.
  • When the owner goes out of scope, the value is automatically cleaned up, releasing the memory.

Scope of Variables:

  • The scope of a variable is the part of the program where the variable is valid and can be used.
  • When a variable goes out of scope, Rust automatically calls the drop function to free the memory.

String Type:

  • String literals are immutable and fixed in size, but String objects are mutable and stored on the heap.
  • The String type is used for dynamic and growable text data.

Memory Management:

  • Rust uses stack and heap memory for different purposes.
  • Stack memory is for fixed-size data and is fast but limited.
  • Heap memory is for data that can grow in size, is slower to access, but more flexible.

Functions and Ownership:

  • When passing variables to functions, Rust can either move or copy the values.
  • Moving a value transfers ownership to the function, making the original variable unusable unless ownership is returned.
  • Copying leaves the original variable usable because the function gets a duplicate.

Returning Values and Scope:

  • Functions can return values, transferring ownership back to the caller.
  • Tuples can be used to return multiple values from a function.

References and Borrowing:

  • References allow functions to use values without taking ownership, which means multiple parts of the code can read or modify data without duplicating it.
  • Rust enforces rules to ensure references are always valid, preventing issues like dangling references.

Mutable References:

  • Mutable references allow functions to modify the borrowed value.
  • Only one mutable reference is allowed at a time to prevent data races and ensure consistency.

Dangling References Prevention:

  • Rust’s borrow checker ensures references are always valid by preventing references to data that has been freed.

Reference Rules:

  • You can have either one mutable reference or multiple immutable references to a value at a time, but not both simultaneously.
  • References must always be valid during their use.

Slice Type:

  • Slices provide a way to view a portion of a collection, like an array or a string, without t aking ownership.
  • They enable working with parts of data efficiently without copying.

Understanding these concepts helps Rust programmers write safe, efficient, and concurrent code, leveraging the ownership system to manage memory effectively and prevent common bugs.

Using Structs to Structure Related Data

Using structs in Rust to create and manage custom data types.

Defining and Instantiating Structs:

  • Structs are similar to tuples in that they group related values, but they are more flexible because you can name each piece of data.
  • To define a struct, use the struct keyword followed by the struct name and fields inside curly brackets.
  • Example:
struct User { active: bool, username: String, email: String, sign_in_count: u64, }
  • Creating an instance of a struct:
let user1 = User { 
active: true, 
username: String::from("user123"), 
email: String::from("user@example.com"), 
sign_in_count: 1, };

Using Structs:

  • Access fields using dot notation: println!("Username: {}", user1.username);
  • To modify a field, the entire instance must be mutable:
let mut user1 = User {
active: true,
username: String::from("user123"), 
email: String::from("user@example.com"), 
sign_in_count: 1, 
};
user1.email = String::from("new@example.com");

Using the Field Init Shorthand:

  • When creating an instance, if the field name and variable name are the same, you can use the shorthand syntax:
fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, } }

Creating Instances from Other Instances with Struct Update Syntax:

  • This allows you to create a new instance by using some values from another instance:
let user2 = User { email: String::from("another@example.com"), ..user1 };

Using Tuple Structs:

  • Tuple structs are similar to tuples but with named types. They don’t have named fields, just types.
struct Color(i32, i32, i32); 
struct Point(i32, i32, i32); 
let black = Color(0, 0, 0); 
let origin = Point(0, 0, 0);

Unit-Like Structs Without Any Fields:

  • These are used when you want to implement a trait on a type but don’t need any data to be stored in the type itself.
struct AlwaysEqual; let subject = AlwaysEqual;

Example Program Using Structs:

  • The chapter includes an example of calculating the area of a rectangle using structs to show how they provide more clarity and structure.
  • Initial version using individual variables:
fn main() { 
let width1 = 30; 
let height1 = 50; 
println!("The area of the rectangle is {} square pixels.", area(width1, height1)); 
} 
fn area(width: u32, height: u32) -> u32 { width * height }
  • Improved version using a tuple:
fn main() { 
let rect1 = (30, 50); 
println!("The area of the rectangle is {} square pixels.", area(rect1)); 
} 
fn area(dimensions: (u32, u32)) -> u32 { dimensions.0 * dimensions.1 }
  • Best version using a struct:
struct Rectangle { width: u32, height: u32, } 
fn main() { 
let rect1 = Rectangle { width: 30, height: 50 }; 
println!("The area of the rectangle is {} square pixels.", area(&rect1)); 
} 
fn area(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height }

Method Syntax:

  • Methods are functions defined within the context of a struct, enum, or trait object.
  • Define methods using impl blocks:
rust impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }
  • Calling a method:
rust let rect1 = Rectangle { width: 30, height: 50 }; 
println!("The area of the rectangle is {} square pixels.", rect1.area());

Associated Functions:

  • Functions within an impl block that don’t take self as a parameter are called associated functions.
  • Commonly used for constructors:
impl Rectangle { 
fn square(size: u32) -> Self { Self { width: size, height: size } } 
} 
let sq = Rectangle::square(3);

Multiple impl Blocks:

  • You can have multiple impl blocks for a single struct.
  • This is useful for organizing related methods together.

Enums and Pattern Matching

How to use enums to create types that can be one of several different variants and how pattern matching can be used to handle these different variants.

Defining Enums:

  • Enums allow you to define a type by enumerating its possible variants.
  • Example:
    rust enum IpAddrKind { V4, V6, }
  • Each variant can optionally have associated data:
    rust enum IpAddr { V4(String), V6(String), }

Using Enums:

  • Enums can be used similarly to structs:
rust let home = IpAddr::V4(String::from("127.0.0.1")); 
let loopback = IpAddr::V6(String::from("::1"));

The Option Enum:

  • The Option enum is a built-in enum used to express a value that might or might not be present.
  • Example:
    rust enum Option<T> { Some(T), None, }
  • It is commonly used in Rust instead of null values found in other languages.

Match Control Flow:

  • The match expression is used to handle different variants of enums.
enum Coin { Penny, Nickel, Dime, Quarter, } 
fn value_in_cents(coin: Coin) -> u32 { 
match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } 
}

Patterns That Bind to Values:

  • Patterns can bind to the values within an enum variant:
rust fn main() { 
let some_number = Some(5); 
match some_number { Some(x) => println!("The number is: {}", x), None => println!("No number"), } 
}

Matching with Option<T>:

  • Matching on Option variants can be used to safely handle optional values.
rust fn plus_one(x: Option<i32>) -> Option<i32> { 
match x { Some(i) => Some(i + 1), None => None, } 
}

Matches Are Exhaustive:

  • All possible cases must be covered when using match. If not, the code will not compile.
  • A catch-all pattern (_) can be used to handle any remaining cases:
rust let some_value = Some(0); 
match some_value { 
Some(0) => println!("Zero"), 
Some(_) => println!("Some other value"), 
None => println!("No value"), 
}

Concise Control Flow with if let:

  • if let can be used for more concise pattern matching when you only care about one of the patterns:
rust let some_value = Some(3); 
if let Some(3) = some_value { println!("Three"); }

Managing Growing Projects with Packages, Crates, and Modules

Packages and Crates:

  • Package: A package is a bundle of one or more crates. A package contains a Cargo.toml file that describes how to build those crates.
  • Crate: The smallest amount of code that the Rust compiler considers at a time. There are two types of crates: binary crates and library crates. A binary crate has a main function and is compiled to an executable, while a library crate does not.

Defining Modules to Control Scope and Privacy:

  • Modules allow you to organize code within a crate into groups for readability and reusability.
  • Example of defining a module:
    rust mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } }
  • Use the pub keyword to make items public and accessible from outside the module.

Paths for Referring to an Item in the Module Tree:

  • Absolute Path: Starts from the crate root.
crate::front_of_house::hosting::add_to_waitlist();
  • Relative Path: Starts from the current module.
front_of_house::hosting::add_to_waitlist();

Exposing Paths with the pub Keyword:

  • Use pub to make a module or function public so it can be accessed from outside the current module.
pub mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } }

Starting Relative Paths with super:

  • Use super to refer to the parent module in a relative path.
  • fn serve_order() { super::deliver_order(); }

Making Structs and Enums Public:

  • By default, structs and enums are private. Use pub to make them public.
pub struct Breakfast { pub toast: String, seasonal_fruit: String, } 
pub enum Appetizer { Soup, Salad, }

Bringing Paths into Scope with the use Keyword:

  • The use keyword allows you to create a shortcut to bring a path into scope.
use crate::front_of_house::hosting; fn main() { hosting::add_to_waitlist(); }

Creating Idiomatic use Paths:

  • It’s common to bring the parent module into scope with use and then refer to the item. This makes it clear which module the item is from.
use crate::front_of_house::hosting; fn main() { hosting::add_to_waitlist(); }

Providing New Names with the as Keyword:

  • Use the as keyword to create an alias for a path.
rust use std::fmt::Result as FmtResult;

Re-exporting Names with pub use:

pub use crate::front_of_house::hosting;

Using External Packages:

Using Nested Paths to Clean Up Large use Lists:

use std::{cmp::Ordering, io};

The Glob Operator:

use std::collections::*;

Separating Modules into Different Files:

// In src/lib.rs mod front_of_house; // In src/front_of_house.rs pub mod hosting { pub fn add_to_waitlist() {} }

Common Collections

Rust’s common collections are powerful tools for managing and storing data.

Vectors:

  • Vectors are used to store a variable number of values next to each other.
  • Example of creating and updating a vector:
    rust let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3);
  • Accessing elements in a vector:
    rust let third = &v[2]; match v.get(2) { Some(third) => println!("The third element is {}", third), None => println!("There is no third element."), }

Iterating Over the Values in a Vector:

  • You can iterate over the values in a vector using a for loop:
    rust let v = vec![100, 32, 57]; for i in &v { println!("{}", i); }
  • Mutable iteration:
    rust let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; }

Using an Enum to Store Multiple Types:

  • Vectors can store multiple types by using an enum: enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ];

Strings:

  • Strings are used for storing UTF-8 encoded text.
  • Creating a new string:
    rust let mut s = String::new(); let data = "initial contents"; let s = data.to_string(); let s = String::from("initial contents");
  • Updating a string:
    rust let mut s = String::from("foo"); s.push_str("bar"); s.push('!');

Concatenation with + Operator or format! Macro:

  • Concatenate strings using +:
    rust let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2;
  • Using format! macro:
    rust let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = format!("{}-{}-{}", s1, s2, s3);

Indexing into Strings:

  • Rust strings cannot be indexed directly due to their internal representation.
  • Slicing strings:
    rust let hello = "Здравствуйте"; let s = &hello[0..4];

Iterating Over Strings:

  • Iterate over the characters or bytes of a string: for c in "नमस्ते".chars() { println!("{}", c); } for b in "नमस्ते".bytes() { println!("{}", b); }

Hash Maps:

  • Hash maps store a mapping of keys to values.
  • Creating a new hash map: use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50);
  • Accessing values in a hash map:
    rust let team_name = String::from("Blue"); let score = scores.get(&team_name);
  • Iterating over key-value pairs:
    rust for (key, value) in &scores { println!("{}: {}", key, value); }

Updating a Hash Map:

  • Overwriting a value:
    rust scores.insert(String::from("Blue"), 25);
  • Adding a key-value pair only if the key isn’t present:
    rust scores.entry(String::from("Yellow")).or_insert(50); scores.entry(String::from("Blue")).or_insert(50);
  • Updating a value based on the old value: let text = "hello world wonderful world"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1; }

Error Handling

How to handle errors effectively in Rust. Here are the key points:

Unrecoverable Errors with panic!:

    • The panic! macro causes the program to crash and display an error message. It is used for unrecoverable errors.
    • Example:
      rust fn main() { panic!("Crash and burn!"); }

    Recoverable Errors with Result:

      • The Result enum is used for recoverable errors and has two variants: Ok and Err.
      • Example:
        rust enum Result<T, E> { Ok(T), Err(E), }
      • Using Result to handle errors: use std::fs::File; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) =&gt; file, Err(error) =&gt; panic!("Problem opening the file: {:?}", error), }; }

      Matching on Different Errors:

        • Handling different kinds of errors using match.
        • Example: use std::fs::File; use std::io::ErrorKind; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) =&gt; file, Err(ref error) if error.kind() == ErrorKind::NotFound =&gt; { match File::create("hello.txt") { Ok(fc) =&gt; fc, Err(e) =&gt; panic!("Problem creating the file: {:?}", e), } }, Err(error) =&gt; { panic!("Problem opening the file: {:?}", error); }, }; }

        Shortcuts for Panic on Error: unwrap and expect:

          • unwrap returns the value inside an Ok or calls panic! if it’s an Err.
          • expect does the same as unwrap, but lets you add a custom error message.
          • Example:
            rust let f = File::open("hello.txt").unwrap(); let f = File::open("hello.txt").expect("Failed to open hello.txt");

          Propagating Errors:

            • Propagating errors allows a function to return an error to the calling code.
            • Using the ? operator to propagate errors: use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }

            To panic! or Not to panic!:

              • Guidelines for when to use panic! and when to return a Result.
              • Use panic! for unrecoverable errors and scenarios where continuing the program would be unsafe.
              • Use Result for recoverable errors and when it is reasonable to continue the program after handling the error.

              Generic Types, Traits, and Lifetimes

              Advanced features of Rust: generics, traits, and lifetimes, which enable code reuse, abstraction, and memory safety.

              Generic Data Types:

                • Generics allow for writing flexible and reusable code for different data types.
                • Example of a generic function:
                  rust fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest }
                • Generics can be used in structs:
                  rust struct Point<T> { x: T, y: T, }

                Traits: Defining Shared Behavior:

                  • Traits define functionality that a type must provide.
                  • Example of defining and implementing a trait: pub trait Summary { fn summarize(&self) -> String; } pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } }

                  Default Implementations:

                    • Traits can provide default implementations for methods.
                    • Example:
                      rust pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } }

                    Traits as Parameters:

                      • Traits can be used to define function parameters that accept any type implementing the trait.
                      • Example:
                        rust pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); }

                      Trait Bounds:

                        • Trait bounds specify that a generic type must implement a particular trait.
                        • Example:
                          rust pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); }

                        Using where Clauses:

                          • For readability, where clauses can be used to specify trait bounds.
                          • Example:
                            rust fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug, { // Function body }

                          Returning Types that Implement Traits:

                            • Functions can return types that implement a specific trait.
                            • Example:
                              rust fn returns_summarizable() -> impl Summary { NewsArticle { headline: String::from("Breaking News!"), location: String::from("New York"), author: String::from("John Doe"), content: String::from("Content of the article..."), } }

                            Validating References with Lifetimes:

                              • Lifetimes ensure that references are valid as long as they are needed.
                              • Example:
                                rust fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

                              Lifetime Annotations in Structs:

                                • Structs can also have lifetime annotations to ensure validity.
                                • Example:
                                  rust struct ImportantExcerpt<'a> { part: &'a str, }

                                Lifetime Elision:

                                • Rust infers lifetimes in common situations to reduce annotation burden.
                                • Three rules of lifetime elision:

                                1. Each parameter that is a reference gets its own lifetime.
                                2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
                                3. If there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters.

                                  Generic Type Parameters, Trait Bounds, and Lifetimes Together:

                                  1. Combining generics, trait bounds, and lifetimes in one function.
                                  2. Example:
                                    rust fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str where T: Display, { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } }

                                  Generic Types, Traits, and Lifetimes

                                  Rust has advanced features of Rust: generics, traits, and lifetimes, which enable code reuse, abstraction, and memory safety.

                                  Generic Data Types:

                                    • Generics allow for writing flexible and reusable code for different data types.
                                    • Example of a generic function:
                                      rust fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest }
                                    • Generics can be used in structs:
                                      rust struct Point<T> { x: T, y: T, }

                                    Traits: Defining Shared Behavior:

                                      • Traits define functionality that a type must provide.
                                      • Example of defining and implementing a trait: pub trait Summary { fn summarize(&self) -> String; } pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } }

                                      Default Implementations:

                                        • Traits can provide default implementations for methods.
                                        • Example:
                                          rust pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } }

                                        Traits as Parameters:

                                          • Traits can be used to define function parameters that accept any type implementing the trait.
                                          • Example:
                                            rust pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); }

                                          Trait Bounds:

                                            • Trait bounds specify that a generic type must implement a particular trait.
                                            • Example:
                                              rust pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); }

                                            Using where Clauses:

                                              • For readability, where clauses can be used to specify trait bounds.
                                              • Example:
                                                rust fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug, { // Function body }

                                              Returning Types that Implement Traits:

                                                • Functions can return types that implement a specific trait.
                                                • Example:
                                                  rust fn returns_summarizable() -> impl Summary { NewsArticle { headline: String::from("Breaking News!"), location: String::from("New York"), author: String::from("John Doe"), content: String::from("Content of the article..."), } }

                                                Validating References with Lifetimes:

                                                  • Lifetimes ensure that references are valid as long as they are needed.
                                                  • Example:
                                                    rust fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

                                                  Lifetime Annotations in Structs:

                                                    • Structs can also have lifetime annotations to ensure validity.
                                                    • Example:
                                                      rust struct ImportantExcerpt<'a> { part: &'a str, }

                                                    Lifetime Elision:

                                                    • Rust infers lifetimes in common situations to reduce annotation burden.
                                                    • Three rules of lifetime elision:

                                                    1. Each parameter that is a reference gets its own lifetime.
                                                    2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
                                                    3. If there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters.

                                                    Generic Type Parameters, Trait Bounds, and Lifetimes Together:

                                                    • Combining generics, trait bounds, and lifetimes in one function.
                                                    • Example:
                                                      rust fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str where T: Display, { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } }

                                                    Reference:

                                                    The Rust Programming Language 2nd Edition (2023) by Klabnik Nichols and Carol Nichols, with contributions from the Rust Community

                                                    Back To Top
                                                    Theme Mode