mirror of
https://github.com/theoludwig/rust_book.git
synced 2024-07-17 08:30:11 +02:00
chore: initial commit
This commit is contained in:
commit
2a41387d8e
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
target
|
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# The Rust Programming Language
|
||||||
|
|
||||||
|
Book to get started with the Rust programming language.
|
||||||
|
|
||||||
|
Available online: <https://doc.rust-lang.org/book/>
|
||||||
|
|
||||||
|
Available offline: `rustup docs --book`
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Rust is a multi-paradigm, general-purpose programming language. Rust emphasizes performance, type safety, and concurrency. Rust enforces memory safety—that is, that all references point to valid memory—without requiring the use of a garbage collector or reference counting present in other memory-safe languages.
|
||||||
|
|
||||||
|
Rust keeps track of who can read and write to memory. It knows when the program is using memory and immediately frees the memory once it is no longer needed.
|
||||||
|
|
||||||
|
- [Website](https://www.rust-lang.org/)
|
||||||
|
- Local Documentation: `rustup doc`
|
||||||
|
- Rust version used: v1.74.0 (`rustc --version`)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
1. [Getting Started](./chapter_1_getting_started)
|
||||||
|
2. [Programming a Guessing Game](./chapter_2_guessing_game)
|
||||||
|
3. [Common Programming Concepts](./chapter_3_common_concepts)
|
||||||
|
4. [Understanding Ownership](./chapter_4_ownership)
|
||||||
|
5. [Using Structs to Structure Related Data](./chapter_5_structs)
|
||||||
|
6. [Enums and Pattern Matching](./chapter_6_enums)
|
||||||
|
7. [Managing Growing Projects with Packages, Crates, and Modules](./chapter_7_packages_crates_modules)
|
||||||
|
8. [Common Collections](./chapter_8_common_collections)
|
||||||
|
9. [Error Handling](./chapter_9_error_handling)
|
||||||
|
10. [Generic Types, Traits, and Lifetimes](./chapter_10_generics_traits_lifetimes)
|
||||||
|
11. [Testing](./chapter_11_testing)
|
||||||
|
12. [An I/O Project: Building a Command Line Program: `minigrep`](./chapter_12_minigrep)
|
||||||
|
13. [Functional Language Features: Iterators and Closures](./chapter_13_iterators_and_closures)
|
7
chapter_10_generics_traits_lifetimes/Cargo.lock
generated
Normal file
7
chapter_10_generics_traits_lifetimes/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_10_generics_traits_lifetimes"
|
||||||
|
version = "1.0.0"
|
6
chapter_10_generics_traits_lifetimes/Cargo.toml
Normal file
6
chapter_10_generics_traits_lifetimes/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_10_generics_traits_lifetimes"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
241
chapter_10_generics_traits_lifetimes/src/main.rs
Normal file
241
chapter_10_generics_traits_lifetimes/src/main.rs
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Generic Types, Traits, and Lifetimes */
|
||||||
|
|
||||||
|
/* Generic Data Types */
|
||||||
|
// Function signatures or structs, that can use many different concrete data types.
|
||||||
|
|
||||||
|
// Generic type parameters in `fn` definitions
|
||||||
|
fn largest<T: PartialOrd>(list: &[T]) -> &T {
|
||||||
|
let mut largest = &list[0];
|
||||||
|
for item in list {
|
||||||
|
// Error: `>` cannot be applied to type `T`
|
||||||
|
// Solution: `PartialOrd` trait (only use types whose values can be ordered)
|
||||||
|
if item > largest {
|
||||||
|
largest = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
largest
|
||||||
|
}
|
||||||
|
let number_list = vec![34, 50, 25, 100, 65];
|
||||||
|
let result = largest(&number_list);
|
||||||
|
println!("The largest number is {}", result);
|
||||||
|
|
||||||
|
let char_list = vec!['y', 'm', 'a', 'q'];
|
||||||
|
let result = largest(&char_list);
|
||||||
|
println!("The largest char is {}", result);
|
||||||
|
|
||||||
|
// Generic type parameters in `struct` definitions
|
||||||
|
struct Point<T> {
|
||||||
|
x: T,
|
||||||
|
y: T,
|
||||||
|
}
|
||||||
|
let integer = Point { x: 5, y: 10 };
|
||||||
|
let float = Point { x: 1.0, y: 4.0 };
|
||||||
|
|
||||||
|
// We can use different concrete types for each generic type parameter
|
||||||
|
struct Point2<T, U> {
|
||||||
|
x: T,
|
||||||
|
y: U,
|
||||||
|
}
|
||||||
|
let both_integer = Point2 { x: 5, y: 10 };
|
||||||
|
let both_float = Point2 { x: 1.0, y: 4.0 };
|
||||||
|
let integer_and_float = Point2 { x: 5, y: 4.0 };
|
||||||
|
|
||||||
|
// Generic type parameters in `enum` definitions
|
||||||
|
enum Option<T> {
|
||||||
|
Some(T),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic type parameters in `impl` blocks
|
||||||
|
impl<T> Point<T> {
|
||||||
|
fn x(&self) -> &T {
|
||||||
|
&self.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let point = Point { x: 5, y: 10 };
|
||||||
|
println!("p.x = {}", point.x());
|
||||||
|
|
||||||
|
// We can also specify constraints on generic types when defining methods on the type. We could, for example, implement methods only on `Point<f32>` instances rather than on `Point<T>` instances.
|
||||||
|
impl Point<f32> {
|
||||||
|
fn distance_from_origin(&self) -> f32 {
|
||||||
|
(self.x.powi(2) + self.y.powi(2)).sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Traits: Defining Shared Behavior */
|
||||||
|
// Defines functionality a particular type has and can share with other types.
|
||||||
|
// We can use trait bounds to specify that a generic type can be any type that has certain behavior.
|
||||||
|
// Similar to interfaces in other languages.
|
||||||
|
|
||||||
|
// Defining a Trait (using `trait`)
|
||||||
|
/* Example:
|
||||||
|
We have multiple structs that hold various kinds and amounts of text: a `NewsArticle` struct that holds a news story filed in a particular location and a `Tweet` that can have at most 140 characters along with metadata like whether it was a retweet or a reply to another tweet.
|
||||||
|
|
||||||
|
We want to make a media aggregator library crate named `aggregator` that can display summaries of data that might be stored in a `NewsArticle` or `Tweet` instance.
|
||||||
|
*/
|
||||||
|
pub trait Summary {
|
||||||
|
fn summarize(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementing a Trait on a Type (using 'for`)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tweet {
|
||||||
|
pub username: String,
|
||||||
|
pub content: String,
|
||||||
|
pub reply: bool,
|
||||||
|
pub retweet: bool,
|
||||||
|
}
|
||||||
|
impl Summary for Tweet {
|
||||||
|
fn summarize(&self) -> String {
|
||||||
|
format!("{}: {}", self.username, self.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tweet = Tweet {
|
||||||
|
username: String::from("horse_ebooks"),
|
||||||
|
content: String::from("of course, as you probably already know, people"),
|
||||||
|
reply: false,
|
||||||
|
retweet: false,
|
||||||
|
};
|
||||||
|
println!("1 new tweet: {}", tweet.summarize());
|
||||||
|
|
||||||
|
/* Restriction: We can implement a trait on a type only if at least one of the trait or the type is local to our crate (coherence, and more specifically the orphan rule).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- We can implement standard library traits like `Display` on a custom type like `Tweet`.
|
||||||
|
- We can also implement `Summary` on `Vec<T>`.
|
||||||
|
- We can't implement external traits on external types. For example, we can't implement the `Display` trait on `Vec<T>` (both defined in the standard library).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Default Implementations
|
||||||
|
pub trait SummaryWithDefault {
|
||||||
|
fn summarize(&self) -> String {
|
||||||
|
String::from("(Read more...)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traits as Parameters (using `impl Trait`)
|
||||||
|
pub fn notify(item: &impl Summary) {
|
||||||
|
println!("Breaking news! {}", item.summarize());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait Bound Syntax
|
||||||
|
// The `impl Trait` syntax works for straightforward cases but is actually syntax sugar for a longer form known as a trait bound.
|
||||||
|
pub fn notify_trait_bound<T: Summary>(item: &T) {
|
||||||
|
println!("Breaking news! {}", item.summarize());
|
||||||
|
}
|
||||||
|
// Trait bounds can express more complexity in others cases than `impl Trait` syntax.
|
||||||
|
// Example:
|
||||||
|
pub fn notify_verbose(item1: &impl Summary, item2: &impl Summary) {}
|
||||||
|
// vs
|
||||||
|
pub fn notify_trait_bound_verbose<T: Summary>(item1: &T, item2: &T) {}
|
||||||
|
|
||||||
|
// Specifying Multiple Trait Bounds with the `+` Syntax
|
||||||
|
pub fn notify_multiple(item: &(impl Summary + Display)) {}
|
||||||
|
|
||||||
|
// Also valid with trait bounds
|
||||||
|
pub fn notify_trait_bound_multiple<T: Summary + Display>(item: &T) {}
|
||||||
|
|
||||||
|
// Clearer Trait Bounds with `where` Clauses
|
||||||
|
fn some_function_verbose<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
// vs
|
||||||
|
fn some_function<T, U>(t: &T, u: &U) -> i32
|
||||||
|
where
|
||||||
|
T: Display + Clone,
|
||||||
|
U: Clone + Debug,
|
||||||
|
{
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returning Types that Implement Traits
|
||||||
|
// Restriction: We can only return a single type. Example: we can't return a `NewsArticle` and a `Tweet` in the same function (even if both implements `Summary`). There is a way to do that covered in Chapter 17.
|
||||||
|
fn returns_summarizable() -> impl Summary {
|
||||||
|
Tweet {
|
||||||
|
username: String::from("horse_ebooks"),
|
||||||
|
content: String::from("of course, as you probably already know, people"),
|
||||||
|
reply: false,
|
||||||
|
retweet: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using Trait Bounds to Conditionally Implement Methods
|
||||||
|
// Example:
|
||||||
|
struct Pair<T> {
|
||||||
|
x: T,
|
||||||
|
y: T,
|
||||||
|
}
|
||||||
|
impl<T> Pair<T> {
|
||||||
|
fn new(x: T, y: T) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Display + PartialOrd> Pair<T> {
|
||||||
|
fn compare_display(&self) {
|
||||||
|
if self.x >= self.y {
|
||||||
|
println!("The largest member is x = {}.", self.x);
|
||||||
|
} else {
|
||||||
|
println!("The largest member is y = {}.", self.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validating References with Lifetimes */
|
||||||
|
// Lifetimes are another kind of generic.
|
||||||
|
// Rather than ensuring that a type has the behavior we want, lifetimes ensure that references are valid as long as we need them to be.
|
||||||
|
// Every reference in Rust has a lifetime, which is the scope for which that reference is valid.
|
||||||
|
|
||||||
|
// Preventing Dangling References with Lifetimes
|
||||||
|
let result;
|
||||||
|
{
|
||||||
|
let x = 5;
|
||||||
|
result = &x;
|
||||||
|
}
|
||||||
|
// println!("result: {}", result); // error: `x` does not live long enough (once `x` goes out of scope, it will be deallocated and the memory will be invalid)
|
||||||
|
|
||||||
|
// Generic Lifetimes in Functions
|
||||||
|
// Lifetime Annotation Syntax
|
||||||
|
// Lifetime annotations don't change how long any of the references live.
|
||||||
|
// Rather, they describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
|
||||||
|
|
||||||
|
// &i32 // a reference
|
||||||
|
// &'a i32 // a reference with an explicit lifetime
|
||||||
|
// &'a mut i32 // a mutable reference with an explicit lifetime
|
||||||
|
|
||||||
|
// Example: Function that returns the longest of two string slices.
|
||||||
|
// Signature express the following constraint:
|
||||||
|
// The returned reference will be valid as long as both the parameters are valid.
|
||||||
|
// => string slices that live at least as long as lifetime `'a`.
|
||||||
|
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||||
|
if x.len() > y.len() {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let string1 = String::from("abcd");
|
||||||
|
let string2 = "xyz";
|
||||||
|
let result = longest(&string1, string2);
|
||||||
|
println!("The longest string is \"{}\".", result);
|
||||||
|
}
|
251
chapter_11_testing/Cargo.lock
generated
Normal file
251
chapter_11_testing/Cargo.lock
generated
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert_cmd"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"bstr",
|
||||||
|
"doc-comment",
|
||||||
|
"predicates",
|
||||||
|
"predicates-core",
|
||||||
|
"predicates-tree",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_11_testing"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"assert_cmd",
|
||||||
|
"predicates",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difflib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "normalize-line-endings"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"difflib",
|
||||||
|
"float-cmp",
|
||||||
|
"itertools",
|
||||||
|
"normalize-line-endings",
|
||||||
|
"predicates-core",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
10
chapter_11_testing/Cargo.toml
Normal file
10
chapter_11_testing/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_11_testing"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_cmd = "2.0.12"
|
||||||
|
predicates = "3.0.4"
|
72
chapter_11_testing/README.md
Normal file
72
chapter_11_testing/README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# 11. Testing
|
||||||
|
|
||||||
|
Tests are Rust functions that verify that the non-test code is functioning in the expected manner.
|
||||||
|
|
||||||
|
The bodies of test functions typically perform these 3 actions:
|
||||||
|
|
||||||
|
1. Set up any needed data or state.
|
||||||
|
1. Run the code you want to test.
|
||||||
|
1. Assert the results are what you expect.
|
||||||
|
|
||||||
|
Command to run tests: `cargo test`.
|
||||||
|
|
||||||
|
## Running Tests in Parallel or Consecutively
|
||||||
|
|
||||||
|
By default, tests are run in parallel using threads, so the order in which test functions are run is not guaranteed.
|
||||||
|
|
||||||
|
If you don't want to run the tests in parallel or if you want more fine-grained control over the number of threads used, you can use `cargo test -- --test-threads=1`.
|
||||||
|
|
||||||
|
## Showing Function Output
|
||||||
|
|
||||||
|
By default, if a test passes, Rust's test library captures anything printed to standard output. For example, if we call `println!` in a test and the test passes, we won't see the `println!` output in the terminal.
|
||||||
|
|
||||||
|
If we want to see printed values for passing tests as well, we can tell Rust to also show the output of successful tests with `cargo test -- --show-output`.
|
||||||
|
|
||||||
|
## Running a Subset of Tests by Name
|
||||||
|
|
||||||
|
Sometimes, running a full test suite can take a long time. If you're working on code in a particular area, you might want to run only the tests pertaining to that code.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
a + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_two_and_two() {
|
||||||
|
assert_eq!(4, add_two(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_three_and_two() {
|
||||||
|
assert_eq!(5, add_two(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_hundred() {
|
||||||
|
assert_eq!(102, add_two(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can run only the tests that have `add_two_and_two` in their names by running `cargo test add_two_and_two`.
|
||||||
|
|
||||||
|
Or, we can run all the tests that have `add` in their names by running `cargo test add`.
|
||||||
|
|
||||||
|
We can also run a particular test in a particular file by running `cargo test <filename>::<test_name>` (e.g. `cargo test tests::test_main_success`) or all tests in a particular file/folder by running `cargo test <filename>` (e.g. `cargo test tests`).
|
||||||
|
|
||||||
|
## Test Organization
|
||||||
|
|
||||||
|
The Rust community thinks about tests in terms of 2 main categories: unit tests and integration tests.
|
||||||
|
|
||||||
|
- **Unit tests** are small and more focused, testing one module in isolation at a time, and can test private interfaces.
|
||||||
|
- **Integration tests** are entirely external to your library and use your code in the same way any other external code would, using only the public interface and potentially exercising multiple modules per test.
|
||||||
|
|
||||||
|
Rust's privacy rules do allow you to test private functions by importing them with `use super::*;` in the `tests` module (only possible in unit tests).
|
||||||
|
|
||||||
|
Integration tests are in the `tests` directory at the crate root.
|
96
chapter_11_testing/src/lib.rs
Normal file
96
chapter_11_testing/src/lib.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
/// Add some `values` together.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `values` is empty.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let values = [1, 2, 3];
|
||||||
|
/// let result = chapter_11_testing::add(&values);
|
||||||
|
/// assert_eq!(result, 6);
|
||||||
|
/// ```
|
||||||
|
pub fn add<T: Add<T, Output = T> + Clone>(values: &[T]) -> T {
|
||||||
|
if values.is_empty() {
|
||||||
|
panic!("`values` should not be empty.");
|
||||||
|
}
|
||||||
|
let mut result = values[0].clone();
|
||||||
|
for item in values.iter().skip(1) {
|
||||||
|
result = result + item.clone();
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Rectangle {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rectangle {
|
||||||
|
pub fn can_hold(&self, other: &Rectangle) -> bool {
|
||||||
|
self.width > other.width && self.height > other.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Rectangle {
|
||||||
|
fn eq(&self, other: &Rectangle) -> bool {
|
||||||
|
self.width == other.width && self.height == other.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
assert_eq!(3, add(&[1, 2]), "1 + 2 should be 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "`values` should not be empty.")]
|
||||||
|
pub fn test_add_panic() {
|
||||||
|
let empty: Vec<i32> = Vec::new();
|
||||||
|
add(&empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rectangle_can_hold() {
|
||||||
|
let rectangle1 = Rectangle {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
};
|
||||||
|
let rectangle2 = Rectangle {
|
||||||
|
width: 5,
|
||||||
|
height: 5,
|
||||||
|
};
|
||||||
|
let rectangle3 = Rectangle {
|
||||||
|
width: 15,
|
||||||
|
height: 15,
|
||||||
|
};
|
||||||
|
assert!(rectangle1.can_hold(&rectangle2));
|
||||||
|
assert!(!rectangle1.can_hold(&rectangle3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_rectangle_equalities() {
|
||||||
|
let rectangle1 = Rectangle {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
};
|
||||||
|
let rectangle2 = Rectangle {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
};
|
||||||
|
let rectangle3 = Rectangle {
|
||||||
|
width: 15,
|
||||||
|
height: 15,
|
||||||
|
};
|
||||||
|
assert_eq!(rectangle1, rectangle2);
|
||||||
|
assert_ne!(rectangle1, rectangle3);
|
||||||
|
}
|
||||||
|
}
|
45
chapter_11_testing/src/main.rs
Normal file
45
chapter_11_testing/src/main.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use chapter_11_testing::add;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Adding `numbers_count` integers together!");
|
||||||
|
|
||||||
|
let mut integers_count = String::from("");
|
||||||
|
print!("`numbers_count`: ");
|
||||||
|
io::stdout().flush().expect("Failed to flush `stdout`.");
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut integers_count)
|
||||||
|
.expect("Failed to read `stdin` line.");
|
||||||
|
let numbers_count: usize = integers_count
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.expect("Failed to convert `numbers_count` as an `usize`.");
|
||||||
|
|
||||||
|
if numbers_count == 0 {
|
||||||
|
panic!("`numbers_count` should not be 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut numbers = Vec::new();
|
||||||
|
for index in 0..numbers_count {
|
||||||
|
let mut number = String::from("");
|
||||||
|
print!("`number{index}`: ");
|
||||||
|
io::stdout().flush().expect("Failed to flush `stdout`.");
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut number)
|
||||||
|
.expect("Failed to read `stdin` line.");
|
||||||
|
let number: i32 = number
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to convert `number{index}` as an `i32`."));
|
||||||
|
numbers.push(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = add(&numbers);
|
||||||
|
let numbers = numbers
|
||||||
|
.iter()
|
||||||
|
.map(|number| number.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" + ");
|
||||||
|
println!("{numbers} = {result}");
|
||||||
|
}
|
31
chapter_11_testing/tests/integration_tests.rs
Normal file
31
chapter_11_testing/tests/integration_tests.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use assert_cmd::Command;
|
||||||
|
use predicates::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_success() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.write_stdin(["3", "1", "2", "3"].join("\n"));
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::contains("1 + 2 + 3 = 6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_failure_invalid_numbers_count() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.write_stdin(["0"].join("\n"));
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::contains("`numbers_count` should not be 0."));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_failure_invalid_number() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.write_stdin(["1", "a"].join("\n"));
|
||||||
|
command.assert().failure().stderr(predicate::str::contains(
|
||||||
|
"Failed to convert `number0` as an `i32`.",
|
||||||
|
));
|
||||||
|
}
|
321
chapter_12_minigrep/Cargo.lock
generated
Normal file
321
chapter_12_minigrep/Cargo.lock
generated
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert_cmd"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"bstr",
|
||||||
|
"doc-comment",
|
||||||
|
"predicates",
|
||||||
|
"predicates-core",
|
||||||
|
"predicates-tree",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_12_minigrep"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"assert_cmd",
|
||||||
|
"colored",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
|
||||||
|
dependencies = [
|
||||||
|
"is-terminal",
|
||||||
|
"lazy_static",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difflib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"difflib",
|
||||||
|
"itertools",
|
||||||
|
"predicates-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
10
chapter_12_minigrep/Cargo.toml
Normal file
10
chapter_12_minigrep/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_12_minigrep"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
colored = "2.0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_cmd = "2.0.12"
|
47
chapter_12_minigrep/README.md
Normal file
47
chapter_12_minigrep/README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# An I/O Project: Building a Command Line Program: `minigrep`
|
||||||
|
|
||||||
|
Command line tool that interacts with file and command line input/output to practice some of the Rust concepts we've learned so far.
|
||||||
|
|
||||||
|
Rust's speed, safety, single binary output, and cross-platform support make it an ideal language for creating command line tools, so for our project, we'll make our own version of the classic command line search tool `grep` (**g**lobally search a **r**egular **e**xpression and **p**rint).
|
||||||
|
|
||||||
|
`grep` takes as its arguments a file path and a string. Then it reads the file, finds lines in that file that contain the string argument, and prints only those lines (containing the string).
|
||||||
|
|
||||||
|
We'll call our project `minigrep` to distinguish it from the `grep` tool that you might already have on your system.
|
||||||
|
|
||||||
|
The first task is to make minigrep accept its two command line arguments: the file path and a string to search for.
|
||||||
|
|
||||||
|
## Example usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic search
|
||||||
|
|
||||||
|
It searches for the string `"the"` in the file `poem.txt` and prints all the lines that included the string to the terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -- "the" "poem.txt"
|
||||||
|
# or
|
||||||
|
IGNORE_CASE="false" cargo run -- "the" "poem.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
By default `IGNORE_CASE` is `false` and the search is **case-sensitive**.
|
||||||
|
|
||||||
|
similar to:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
grep "the" "poem.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Case-Insensitive search
|
||||||
|
|
||||||
|
```sh
|
||||||
|
IGNORE_CASE="true" cargo run -- "the" "poem.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
similar to:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
grep -i "the" "poem.txt"
|
||||||
|
```
|
9
chapter_12_minigrep/poem.txt
Normal file
9
chapter_12_minigrep/poem.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
I'm nobody! Who are you?
|
||||||
|
Are you nobody, too?
|
||||||
|
Then there's a pair of us - don't tell!
|
||||||
|
They'd banish us, you know.
|
||||||
|
|
||||||
|
How dreary to be somebody!
|
||||||
|
How public, like a frog
|
||||||
|
To tell your name the livelong day
|
||||||
|
To an admiring bog!
|
32
chapter_12_minigrep/src/config.rs
Normal file
32
chapter_12_minigrep/src/config.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Config {
|
||||||
|
pub query: String,
|
||||||
|
pub file_path: String,
|
||||||
|
pub ignore_case: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn build(mut arguments: impl Iterator<Item = String>) -> Result<Config, &'static str> {
|
||||||
|
arguments.next();
|
||||||
|
let error_message = "Usage: minigrep <query> <file_path>";
|
||||||
|
let query = match arguments.next() {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return Err(error_message),
|
||||||
|
};
|
||||||
|
let file_path = match arguments.next() {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return Err(error_message),
|
||||||
|
};
|
||||||
|
let ignore_case = match env::var("IGNORE_CASE") {
|
||||||
|
Ok(value) => value == "true",
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
Ok(Config {
|
||||||
|
query,
|
||||||
|
file_path,
|
||||||
|
ignore_case,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
27
chapter_12_minigrep/src/error.rs
Normal file
27
chapter_12_minigrep/src/error.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RunError {
|
||||||
|
InputOutputError(std::io::Error),
|
||||||
|
Other(Box<dyn Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RunError {
|
||||||
|
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
RunError::InputOutputError(error) => {
|
||||||
|
write!(formatter, "{error}")
|
||||||
|
}
|
||||||
|
RunError::Other(error) => write!(formatter, "{error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for RunError {}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for RunError {
|
||||||
|
fn from(error: std::io::Error) -> Self {
|
||||||
|
RunError::InputOutputError(error)
|
||||||
|
}
|
||||||
|
}
|
43
chapter_12_minigrep/src/lib.rs
Normal file
43
chapter_12_minigrep/src/lib.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use colored::*;
|
||||||
|
use config::Config;
|
||||||
|
use error::RunError;
|
||||||
|
use search::{search, search_case_insensitive};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
pub mod search;
|
||||||
|
|
||||||
|
pub fn run(config: &Config) -> Result<(), RunError> {
|
||||||
|
let file_content = fs::read_to_string(&config.file_path)?;
|
||||||
|
let mut lines: Vec<String> = file_content.lines().map(|line| line.to_string()).collect();
|
||||||
|
let matching_items = if config.ignore_case {
|
||||||
|
search_case_insensitive(&config.query, &lines)
|
||||||
|
} else {
|
||||||
|
search(&config.query, &lines)
|
||||||
|
};
|
||||||
|
let mut lines_indexes_differences_colored: Vec<usize> = vec![0; lines.len()];
|
||||||
|
let mut matching_line_indexes: Vec<usize> = Vec::new();
|
||||||
|
for item in matching_items {
|
||||||
|
let has_already_matched = matching_line_indexes.contains(&item.line_index);
|
||||||
|
if !has_already_matched {
|
||||||
|
matching_line_indexes.push(item.line_index);
|
||||||
|
}
|
||||||
|
let line_index_difference_colored = lines_indexes_differences_colored[item.line_index];
|
||||||
|
let start_index = item.start_index + line_index_difference_colored;
|
||||||
|
let end_index = item.end_index + line_index_difference_colored;
|
||||||
|
let matching_text = lines[item.line_index]
|
||||||
|
.get(start_index..=end_index)
|
||||||
|
.expect("Failed to get `matching_text`.");
|
||||||
|
let colored_text = matching_text.red().bold().to_string();
|
||||||
|
lines_indexes_differences_colored[item.line_index] +=
|
||||||
|
colored_text.len() - matching_text.len();
|
||||||
|
lines[item.line_index].replace_range(start_index..=end_index, &colored_text);
|
||||||
|
}
|
||||||
|
for (index, line) in lines.iter().enumerate() {
|
||||||
|
if matching_line_indexes.contains(&index) {
|
||||||
|
println!("{line}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
31
chapter_12_minigrep/src/main.rs
Normal file
31
chapter_12_minigrep/src/main.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use chapter_12_minigrep::config::Config;
|
||||||
|
use chapter_12_minigrep::error::RunError;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let config = Config::build(env::args()).unwrap_or_else(|error| {
|
||||||
|
eprintln!("{error}");
|
||||||
|
process::exit(1);
|
||||||
|
});
|
||||||
|
match chapter_12_minigrep::run(&config) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(error) => match error {
|
||||||
|
RunError::InputOutputError(error) => {
|
||||||
|
match error.kind() {
|
||||||
|
ErrorKind::NotFound => {
|
||||||
|
eprintln!("Error: File `{}` not found.", config.file_path)
|
||||||
|
}
|
||||||
|
_ => eprintln!("Error: {error}"),
|
||||||
|
}
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
RunError::Other(error) => {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
121
chapter_12_minigrep/src/search.rs
Normal file
121
chapter_12_minigrep/src/search.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct MatchingItem {
|
||||||
|
pub start_index: usize,
|
||||||
|
pub end_index: usize,
|
||||||
|
pub line_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// With Iterator
|
||||||
|
pub fn search(query: &str, lines: &[String]) -> Vec<MatchingItem> {
|
||||||
|
lines
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(line_index, line)| {
|
||||||
|
line.char_indices().filter_map(move |(index, character)| {
|
||||||
|
query.chars().next().and_then(|first_character| {
|
||||||
|
if first_character == character {
|
||||||
|
let end_index = index + (query.len() - 1);
|
||||||
|
line.get(index..=end_index).and_then(|matching_text| {
|
||||||
|
if matching_text == query {
|
||||||
|
Some(MatchingItem {
|
||||||
|
start_index: index,
|
||||||
|
end_index,
|
||||||
|
line_index,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without Iterator
|
||||||
|
// pub fn search(query: &str, lines: &[String]) -> Vec<MatchingItem> {
|
||||||
|
// let mut matching_items: Vec<MatchingItem> = Vec::new();
|
||||||
|
// for (line_index, line) in lines.iter().enumerate() {
|
||||||
|
// for (index, character) in line.char_indices() {
|
||||||
|
// if query
|
||||||
|
// .chars()
|
||||||
|
// .next()
|
||||||
|
// .is_some_and(|first_character| first_character == character)
|
||||||
|
// {
|
||||||
|
// let end_index = index + (query.len() - 1);
|
||||||
|
// let matching_text = line.get(index..=end_index);
|
||||||
|
// if matching_text.is_some_and(|text| text == query) {
|
||||||
|
// matching_items.push(MatchingItem {
|
||||||
|
// start_index: index,
|
||||||
|
// end_index,
|
||||||
|
// line_index,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// matching_items
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn search_case_insensitive(query: &str, lines: &[String]) -> Vec<MatchingItem> {
|
||||||
|
let query_lowercase = query.to_lowercase();
|
||||||
|
let mut lines_lowercase: Vec<String> = Vec::new();
|
||||||
|
for line in lines {
|
||||||
|
lines_lowercase.push(line.to_lowercase());
|
||||||
|
}
|
||||||
|
search(&query_lowercase, &lines_lowercase)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn case_sensitive() {
|
||||||
|
let query = "duct";
|
||||||
|
let lines = [
|
||||||
|
"Rust:".to_string(),
|
||||||
|
"safe, fast, productive.".to_string(),
|
||||||
|
"Pick three.".to_string(),
|
||||||
|
"Duct tape.".to_string(),
|
||||||
|
];
|
||||||
|
let expected = vec![MatchingItem {
|
||||||
|
start_index: 15,
|
||||||
|
end_index: 18,
|
||||||
|
line_index: 1,
|
||||||
|
}];
|
||||||
|
let actual = search(query, &lines);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert_eq!(query, lines[1].get(15..=18).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn case_insensitive() {
|
||||||
|
let query = "rUsT";
|
||||||
|
let lines = [
|
||||||
|
"Rust:".to_string(),
|
||||||
|
"safe, fast, productive.".to_string(),
|
||||||
|
"Pick three.".to_string(),
|
||||||
|
"Trust me.".to_string(),
|
||||||
|
];
|
||||||
|
let expected = vec![
|
||||||
|
MatchingItem {
|
||||||
|
start_index: 0,
|
||||||
|
end_index: 3,
|
||||||
|
line_index: 0,
|
||||||
|
},
|
||||||
|
MatchingItem {
|
||||||
|
start_index: 1,
|
||||||
|
end_index: 4,
|
||||||
|
line_index: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let actual = search_case_insensitive(query, &lines);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert_eq!("Rust", lines[0].get(0..=3).unwrap());
|
||||||
|
assert_eq!("rust", lines[3].get(1..=4).unwrap());
|
||||||
|
}
|
||||||
|
}
|
53
chapter_12_minigrep/tests/integration_tests.rs
Normal file
53
chapter_12_minigrep/tests/integration_tests.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use assert_cmd::Command;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_success_default() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.args(["the", "poem.txt"]);
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("Then there\'s a pair of us - don\'t tell!\nTo tell your name the livelong day\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_success_case_sensitive() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.args(["I", "poem.txt"]);
|
||||||
|
command.env_clear().env("IGNORE_CASE", "false");
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("I\'m nobody! Who are you?\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_success_case_insensitive() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.args(["I", "poem.txt"]);
|
||||||
|
command.env_clear().env("IGNORE_CASE", "true");
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("I\'m nobody! Who are you?\nThen there\'s a pair of us - don\'t tell!\nThey\'d banish us, you know.\nHow public, like a frog\nTo tell your name the livelong day\nTo an admiring bog!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_failure_invalid_arguments() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.args(["the"]);
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.failure()
|
||||||
|
.stderr("Usage: minigrep <query> <file_path>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_main_failure_invalid_file_path() {
|
||||||
|
let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
|
||||||
|
command.args(["the", "invalid_file_path.txt"]);
|
||||||
|
command
|
||||||
|
.assert()
|
||||||
|
.failure()
|
||||||
|
.stderr("Error: File `invalid_file_path.txt` not found.\n");
|
||||||
|
}
|
79
chapter_13_iterators_and_closures/Cargo.lock
generated
Normal file
79
chapter_13_iterators_and_closures/Cargo.lock
generated
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_13_iterators_and_closures"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"strum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.70"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.25.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
7
chapter_13_iterators_and_closures/Cargo.toml
Normal file
7
chapter_13_iterators_and_closures/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_13_iterators_and_closures"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
strum = { version = "0.25", features = ["derive"] }
|
73
chapter_13_iterators_and_closures/src/main.rs
Normal file
73
chapter_13_iterators_and_closures/src/main.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use crate::shirts::ShirtColor;
|
||||||
|
use crate::shirts::ShirtsInventory;
|
||||||
|
|
||||||
|
pub mod shirts;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Functional Language Features: Iterators and Closures */
|
||||||
|
// - Closures, a function-like construct you can store in a variable.
|
||||||
|
// - Iterators, a way of processing a series of elements.
|
||||||
|
|
||||||
|
// Exercise Example:
|
||||||
|
// - Our t-shirt company gives away an exclusive, limited-edition shirt to someone on our mailing list as a promotion.
|
||||||
|
// - People on the mailing list can optionally add their favorite color to their profile.
|
||||||
|
// - If the person chosen for a free shirt has their favorite color set, they get that color shirt.
|
||||||
|
// - If the person hasn't specified a favorite color, they get whatever color the company currently has the most of.
|
||||||
|
let closure = |num: u32| -> u32 { num };
|
||||||
|
closure(5);
|
||||||
|
fn add_one_v1(x: u32) -> u32 {
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
let add_one_v2 = |x: u32| -> u32 { x + 1 };
|
||||||
|
let add_one_v3 = |x: i32| x + 1;
|
||||||
|
|
||||||
|
add_one_v1(5);
|
||||||
|
add_one_v2(5);
|
||||||
|
add_one_v3(5);
|
||||||
|
|
||||||
|
let store = ShirtsInventory {
|
||||||
|
shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_preference_1 = Some(ShirtColor::Red);
|
||||||
|
let giveaway_1 = store.giveaway(user_preference_1);
|
||||||
|
println!(
|
||||||
|
"The user with preference {:?} gets {:?}",
|
||||||
|
user_preference_1, giveaway_1
|
||||||
|
);
|
||||||
|
|
||||||
|
let user_preference_2 = None;
|
||||||
|
let giveaway_2 = store.giveaway(user_preference_2);
|
||||||
|
println!(
|
||||||
|
"The user with preference {:?} gets {:?}",
|
||||||
|
user_preference_2, giveaway_2
|
||||||
|
);
|
||||||
|
|
||||||
|
// Defining a function that takes a closure
|
||||||
|
// - `FnOnce` applies to closures that can be called once. All closures implement at least this trait, because all closures can be called. A closure that moves captured values out of its body will only implement FnOnce and none of the other Fn traits, because it can only be called once.
|
||||||
|
// - `FnMut` applies to closures that don't move captured values out of their body, but that might mutate the captured values. These closures can be called more than once.
|
||||||
|
// - `Fn` applies to closures that don't move captured values out of their body and that don't mutate captured values, as well as closures that capture nothing from their environment. These closures can be called more than once without mutating their environment, which is important in cases such as calling a closure multiple times concurrently.
|
||||||
|
fn example<F: Fn()>(f: F) {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
example(|| println!("This is a closure!"));
|
||||||
|
|
||||||
|
// Iterators
|
||||||
|
// Allows you to perform some task on a sequence of items.
|
||||||
|
let numbers: Vec<i32> = vec![1, 2, 3];
|
||||||
|
let mut numbers_iterator = numbers.iter();
|
||||||
|
for (index, number) in numbers_iterator.enumerate() {
|
||||||
|
println!("number{index}: {number}");
|
||||||
|
}
|
||||||
|
|
||||||
|
numbers_iterator = numbers.iter();
|
||||||
|
assert_eq!(numbers_iterator.next(), Some(&1));
|
||||||
|
assert_eq!(numbers_iterator.next(), Some(&2));
|
||||||
|
assert_eq!(numbers_iterator.next(), Some(&3));
|
||||||
|
assert_eq!(numbers_iterator.next(), None);
|
||||||
|
|
||||||
|
// Methods that Produce Other Iterators (e.g: `.map`)
|
||||||
|
// `.collect` is a method that takes an iterator and collects the resulting items into a collection data type.
|
||||||
|
let vector: Vec<_> = numbers.iter().map(|number| number + 1).collect();
|
||||||
|
assert_eq!(vector, vec![2, 3, 4]);
|
||||||
|
}
|
73
chapter_13_iterators_and_closures/src/shirts.rs
Normal file
73
chapter_13_iterators_and_closures/src/shirts.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use strum::EnumIter;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, EnumIter, Copy, Clone)]
|
||||||
|
pub enum ShirtColor {
|
||||||
|
Red,
|
||||||
|
Blue,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TotalOccurrences<T: PartialEq> {
|
||||||
|
fn total_occurrences(&self, value: T) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq> TotalOccurrences<T> for Vec<T> {
|
||||||
|
fn total_occurrences(&self, value: T) -> usize {
|
||||||
|
self.iter().filter(|&item| *item == value).count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShirtsInventory {
|
||||||
|
pub shirts: Vec<ShirtColor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShirtsInventory {
|
||||||
|
pub fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
|
||||||
|
user_preference.unwrap_or_else(|| self.most_stocked())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`ShirtColor`] variant that occurs the most in this [`ShirtsInventory`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the [`ShirtColor`] enum has no variants.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let store = ShirtsInventory {
|
||||||
|
/// shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
|
||||||
|
/// };
|
||||||
|
/// assert_eq!(ShirtColor::Blue, store.most_stocked());
|
||||||
|
/// ```
|
||||||
|
pub fn most_stocked(&self) -> ShirtColor {
|
||||||
|
let mut iter = ShirtColor::iter();
|
||||||
|
let mut result = iter
|
||||||
|
.next()
|
||||||
|
.expect("`ShirtColor` enum should have at least one enum variant.");
|
||||||
|
let mut result_total = self.shirts.total_occurrences(result);
|
||||||
|
for shirt_color in iter {
|
||||||
|
let shirt_color_total = self.shirts.total_occurrences(shirt_color);
|
||||||
|
if shirt_color_total > result_total {
|
||||||
|
result = shirt_color;
|
||||||
|
result_total = shirt_color_total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_total_occurrences() {
|
||||||
|
let numbers: Vec<i32> = vec![1, 2, 3, 3, 3];
|
||||||
|
assert_eq!(numbers.total_occurrences(1), 1);
|
||||||
|
assert_eq!(numbers.total_occurrences(2), 1);
|
||||||
|
assert_eq!(numbers.total_occurrences(3), 3);
|
||||||
|
assert_eq!(numbers.total_occurrences(4), 0);
|
||||||
|
}
|
||||||
|
}
|
7
chapter_1_getting_started/Cargo.lock
generated
Normal file
7
chapter_1_getting_started/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_1_getting_started"
|
||||||
|
version = "1.0.0"
|
6
chapter_1_getting_started/Cargo.toml
Normal file
6
chapter_1_getting_started/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_1_getting_started"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
46
chapter_1_getting_started/README.md
Normal file
46
chapter_1_getting_started/README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# 1. Getting Started
|
||||||
|
|
||||||
|
To install Rust, we use `rustup`, a command line tool for managing Rust versions and associated tools.
|
||||||
|
|
||||||
|
## Hello, world
|
||||||
|
|
||||||
|
Rust use `snake_case` as convention for naming variables, functions, modules, etc.
|
||||||
|
|
||||||
|
Filename: `main.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rustc main.rs
|
||||||
|
./main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cargo commands
|
||||||
|
|
||||||
|
=> Cargo as Convention.
|
||||||
|
|
||||||
|
- `cargo new hello_world` to create a new project called `hello_world`: `--bin` to create a binary project (default), `--lib` to create a library project
|
||||||
|
- `cargo build` to compile (development)
|
||||||
|
- `./target/debug/hello_world` to execute (development)
|
||||||
|
- `cargo run` to compile and execute (development)
|
||||||
|
- `cargo test` to run tests
|
||||||
|
- `cargo doc --open` to generate documentation provided by all your dependencies locally and open it in your browser
|
||||||
|
- `cargo check` checks your code to make sure it compiles but doesn't produce an executable
|
||||||
|
- `cargo clippy` runs the clippy (official) linter (is a superset of `cargo check`, so it also checks for compilation errors), `cargo clippy --fix` to automatically fix some of the errors/warnings
|
||||||
|
- `cargo fmt` to format the code
|
||||||
|
- `cargo build --release` to compile with optimizations (production)
|
||||||
|
- `./target/release/hello_world` to execute (production)
|
||||||
|
- `cargo run --release` to compile and run (production)
|
||||||
|
|
||||||
|
## Typical CI pipeline for Rust with Cargo
|
||||||
|
|
||||||
|
- `cargo build --verbose`
|
||||||
|
- `cargo test --verbose`
|
||||||
|
- `cargo clippy --verbose -- -D warnings`
|
||||||
|
- `cargo fmt -- --check`
|
42
chapter_1_getting_started/src/main.rs
Normal file
42
chapter_1_getting_started/src/main.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Scope of a program => prelude
|
||||||
|
// If a type you want to use isn't in the prelude, you have to bring that type into scope explicitly with a `use` statement.
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare a function named `main`.
|
||||||
|
* The `main` function => first code that runs.
|
||||||
|
* The `main` function doesn't need to return a value.
|
||||||
|
*/
|
||||||
|
fn main() {
|
||||||
|
/* General */
|
||||||
|
// `println!` is a macro to print to stdout (`!` => means it is a macro instead of a normal function)
|
||||||
|
println!("Hello world!");
|
||||||
|
print!("Hello, world!\n");
|
||||||
|
|
||||||
|
// Get the user's input
|
||||||
|
let mut some_variable = String::new();
|
||||||
|
// If we hadn't imported the `io` library with `use std::io;` we could use `std::io::stdin`
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut some_variable)
|
||||||
|
.expect("Failed to read `stdin` line.");
|
||||||
|
|
||||||
|
// Printing Values with Placeholders
|
||||||
|
print!("Your input: {some_variable}"); // only working with variable name between {}
|
||||||
|
print!("Your input: {}", some_variable); // result of evaluating an expression
|
||||||
|
|
||||||
|
// Get user's input and convert it to a number
|
||||||
|
print!("Please input a number: ");
|
||||||
|
|
||||||
|
// `flush` => make sure the output is printed to the screen immediately before we start waiting for input (causes the buffer to be written without having completed the line with '\n')
|
||||||
|
io::stdout().flush().expect("Failed to flush `stdout`.");
|
||||||
|
|
||||||
|
let mut number = String::new();
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut number)
|
||||||
|
.expect("Failed to read `stdin` line.");
|
||||||
|
|
||||||
|
// `trim` => remove the newline character ('\n'), if we don't do it, the `parse` will fail as it can't convert `"5\n"` to a number (`5` as an example)
|
||||||
|
// `parse` => convert the string to a number (`u32`)
|
||||||
|
let number: u32 = number.trim().parse().expect("Please type a number!");
|
||||||
|
println!("Your number: {}", number);
|
||||||
|
}
|
75
chapter_2_guessing_game/Cargo.lock
generated
Normal file
75
chapter_2_guessing_game/Cargo.lock
generated
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_2_guessing_game"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
7
chapter_2_guessing_game/Cargo.toml
Normal file
7
chapter_2_guessing_game/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_2_guessing_game"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8.5"
|
34
chapter_2_guessing_game/src/main.rs
Normal file
34
chapter_2_guessing_game/src/main.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use rand::Rng;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Guess the number!");
|
||||||
|
let secret_number = rand::thread_rng().gen_range(1..=100);
|
||||||
|
let mut number_of_try = 0;
|
||||||
|
loop {
|
||||||
|
number_of_try += 1;
|
||||||
|
println!("Please input your guess.");
|
||||||
|
let mut guess = String::new();
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut guess)
|
||||||
|
.expect("Failed to read `stdin` line.");
|
||||||
|
let guess: u8 = match guess.trim().parse() {
|
||||||
|
Ok(num) => num,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
println!("You guessed: {guess}");
|
||||||
|
match guess.cmp(&secret_number) {
|
||||||
|
Ordering::Less => {
|
||||||
|
println!("Too small!");
|
||||||
|
}
|
||||||
|
Ordering::Greater => {
|
||||||
|
println!("Too big!");
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
println!("You win with {number_of_try} numbers of try");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
chapter_3_common_concepts/Cargo.lock
generated
Normal file
7
chapter_3_common_concepts/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_3_common_concepts"
|
||||||
|
version = "1.0.0"
|
6
chapter_3_common_concepts/Cargo.toml
Normal file
6
chapter_3_common_concepts/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_3_common_concepts"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
344
chapter_3_common_concepts/src/main.rs
Normal file
344
chapter_3_common_concepts/src/main.rs
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Variables */
|
||||||
|
// Variables are declared with the `let` keyword.
|
||||||
|
// By default variables are immutable (once a value is bound to a name, you can't change that value).
|
||||||
|
let x = 10;
|
||||||
|
|
||||||
|
// Not allowed (immutable):
|
||||||
|
// let x: i32 = 5;
|
||||||
|
// x = 6;
|
||||||
|
|
||||||
|
// Allowed (`mut` make variable mutable):
|
||||||
|
let mut x: i32 = 5;
|
||||||
|
x = 6;
|
||||||
|
|
||||||
|
// Get the size in bytes of a variable
|
||||||
|
println!("{}", std::mem::size_of_val(&x));
|
||||||
|
|
||||||
|
// Get the size in bytes of a type
|
||||||
|
println!("{}", std::mem::size_of::<i32>());
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
/* Like immutable variables, but with differences:
|
||||||
|
- You can't use `mut` (always immutable).
|
||||||
|
- Can only be set to a constant expression, not the result of a value that could only be computed at runtime (compiler is able to evaluate a limited set of operations at compile time).
|
||||||
|
*/
|
||||||
|
const HOUR_IN_SECONDS: u32 = 60 * 60;
|
||||||
|
|
||||||
|
// Shadowing
|
||||||
|
/* Declare a new variable with the same name, different from mutable variables:
|
||||||
|
- By using `let`, we can perform a few transformations on a value but have the variable be immutable after those transformations have been completed.
|
||||||
|
- We can also change the type of the variable but reuse the same name.
|
||||||
|
*/
|
||||||
|
// Example: We have a string that we want to convert to integer, with a variable with the same name
|
||||||
|
let number = "58";
|
||||||
|
let number: i32 = number.parse().expect("Parsing failed");
|
||||||
|
let number = number + 1;
|
||||||
|
println!("{number}"); // 59
|
||||||
|
|
||||||
|
/* Data Types */
|
||||||
|
// Rust is a statically typed language => know the types of all variables at compile time.
|
||||||
|
// 2 data type subsets: Scalar and Compound.
|
||||||
|
// Can usually infer the type except in cases when many types are possible (e.g: convert a String to a numeric type)
|
||||||
|
let guess: u32 = "42".parse().expect("Not a number!");
|
||||||
|
|
||||||
|
// Scalar Types
|
||||||
|
// Represents a single value. Rust has 4 primary scalar types: integers, floating-point numbers, booleans, and characters.
|
||||||
|
|
||||||
|
// Integer Types
|
||||||
|
// Number without a fractional component.
|
||||||
|
// Signed integer types start with `i` ; Unsigned integer types start with `u`
|
||||||
|
// Default: `i32`
|
||||||
|
|
||||||
|
// 8 bits (1 byte)
|
||||||
|
let number: i8 = -5; // -128 to 127 (from -2^7 to 2^7 - 1)
|
||||||
|
let number: u8 = 5; // 0 to 255 (2^8 = 256)
|
||||||
|
|
||||||
|
// 16 bits (2 bytes)
|
||||||
|
let number: i16 = -5; // -32 768 to 32 767
|
||||||
|
let number: u16 = 5; // 0 to 65 535 (2^16 = 65 536)
|
||||||
|
|
||||||
|
// 32 bits (4 bytes)
|
||||||
|
let number: i32 = -5; // -2 147 483 648 to 2 147 483 647
|
||||||
|
let number: u32 = 5; // 0 to 4 294 967 295 (2^32 = 4 294 967 296)
|
||||||
|
|
||||||
|
// 64 bits (8 bytes)
|
||||||
|
let number: i64 = -5; // (from -2^63 to 2^63 - 1)
|
||||||
|
let number: u64 = 5;
|
||||||
|
|
||||||
|
// 128 bits (16 bytes)
|
||||||
|
let number: i128 = -5;
|
||||||
|
let number: u128 = 5;
|
||||||
|
|
||||||
|
// Arch (Depend on the architecture of the computer your program is running on: 64 bits if you're on a 64-bit architecture and 32 bits if you're on a 32-bit architecture).
|
||||||
|
// Often used for indexing some sort of collection.
|
||||||
|
let number: isize = -5;
|
||||||
|
let number: usize = 5;
|
||||||
|
|
||||||
|
// Integer Literals
|
||||||
|
let decimal = 98_222; // (`_` as a visual separator to make the number easier to read)
|
||||||
|
let hexadecimal = 0xff; // 255
|
||||||
|
let octal = 0o77; // 63
|
||||||
|
let binary = 0b1111_0000; // 240
|
||||||
|
let byte = b'A'; // 65
|
||||||
|
|
||||||
|
// Integer Overflow
|
||||||
|
// Rust uses the term panicking when a program exits with an error (e.g: Integer Overflow).
|
||||||
|
// Type `u8` that can hold values between 0 and 255, but if we try to store 256 in a `u8`, we'll get an error:
|
||||||
|
// let number: u8 = 255;
|
||||||
|
// let number = number + 1;
|
||||||
|
/* To explicitly handle the possibility of overflow, you can use these families of methods provided by the standard library for primitive numeric types:
|
||||||
|
- Wrap in all modes with the `wrapping_*` methods, such as `wrapping_add`.
|
||||||
|
- Return the `None` value if there is overflow with the `checked_*` methods.
|
||||||
|
- Return the value and a boolean indicating whether there was overflow with the `overflowing_*` methods.
|
||||||
|
- Saturate at the value's minimum or maximum values with the `saturating_*` methods.
|
||||||
|
*/
|
||||||
|
let guess = "256";
|
||||||
|
let guess: u8 = guess.parse().expect("Not a number!"); // In this case, `parse` returns the `Err` variant and the program will crash and display the message.
|
||||||
|
|
||||||
|
// Floating-Point Types
|
||||||
|
// Numbers with decimal points.
|
||||||
|
// All floating-point types are signed.
|
||||||
|
// Floating-point numbers are represented according to the IEEE-754 standard.
|
||||||
|
// Default: `f64`
|
||||||
|
let x: f64 = 2.0; // f64 (double precision)
|
||||||
|
let y: f32 = 3.0; // f32 (single precision)
|
||||||
|
|
||||||
|
// Numeric Operations
|
||||||
|
// Rust supports the basic mathematical operations
|
||||||
|
let addition = 5 + 10;
|
||||||
|
let subtraction = 95.5 - 4.3;
|
||||||
|
let multiplication = 4 * 30;
|
||||||
|
let division = 56.7 / 32.2;
|
||||||
|
let remainder = 43 % 5;
|
||||||
|
|
||||||
|
// Boolean Type (1 byte)
|
||||||
|
let true_value: bool = true;
|
||||||
|
let false_value: bool = false;
|
||||||
|
|
||||||
|
// Character Type (4 bytes)
|
||||||
|
// Most primitive alphabetic type: Represents a Unicode Scalar Value (not UTF-8, it is always 4 bytes) => can represent a lot more than just ASCII.
|
||||||
|
// Encoding-independent.
|
||||||
|
// Declared with single quotes.
|
||||||
|
let heart_eyed_cat: char = '😻';
|
||||||
|
/* Side note:
|
||||||
|
'U+xxxx' notation is used to refer to Unicode Codepoints. (e.g: 'U+0041' is the Unicode Codepoint for the 'A' character)
|
||||||
|
|
||||||
|
Unicode idea is to always provide the base characters and the combining characters (e.g: 'é', is combined with 'e' U+0065 and U+0301 the accent itself) separated.
|
||||||
|
But Unicode important goal was to have round-trip compatibility for existing, widely-used encodings (like ASCII), so some pre-composed characters were included (e.g: 'é' already included with ASCII).
|
||||||
|
|
||||||
|
That means "é" != "é", even if correctly working Unicode-capable should render the same way and be treated the same way, so the user doesn't notice the difference.
|
||||||
|
However the difference is noticeable in the code:
|
||||||
|
- 'é' is stored as 1 character (UTF-8 stored with 2 bytes, ASCII stored with 1 byte)
|
||||||
|
- "é" is stored as 2 characters, therefore not possible to store in a `char` but must be in a `String` (UTF-8 stored with 3 bytes)
|
||||||
|
|
||||||
|
Also, even if pre-composed characters in ASCII and UTF-8 can have the same decimal value (e.g: 'é', 233), it does not take the same amount of bytes to represent it in memory as the way to represent it in binary differs between ASCII and UTF-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Compound Types
|
||||||
|
// Compound types can group multiple values into one type. Rust has 2 primitive compound types: tuples and arrays.
|
||||||
|
|
||||||
|
// Tuple Type
|
||||||
|
// General way of grouping together values.
|
||||||
|
let mut tuple: (i32, f64, u8) = (500, 6.4, 1);
|
||||||
|
|
||||||
|
// Accessing Tuple Values
|
||||||
|
let x = tuple.0; // acessing value at index 0 => 500
|
||||||
|
let y = tuple.1; // acessing value at index 1 => 6.4
|
||||||
|
tuple.1 = 23.4; // changing value at index 1 (possible thanks to `mut`) => (500, 23.4, 1)
|
||||||
|
|
||||||
|
// Destructuring
|
||||||
|
let (x, y, z) = tuple;
|
||||||
|
|
||||||
|
// Swapping Values
|
||||||
|
let a = 5;
|
||||||
|
let b = 10;
|
||||||
|
let (a, b) = (b, a);
|
||||||
|
|
||||||
|
// Unit value => Tuple without any value.
|
||||||
|
// Written as `()`.
|
||||||
|
// Represent an empty value or an empty return type (default return type of functions).
|
||||||
|
|
||||||
|
// Array Type
|
||||||
|
// Collection of multiple values.
|
||||||
|
// Unlike a tuple, every element of an array must have the same type
|
||||||
|
// Arrays in Rust have a fixed length.
|
||||||
|
// Useful when you want your data allocated on the stack rather than the heap.
|
||||||
|
let array: [i32; 5] = [1, 2, 3, 4, 5];
|
||||||
|
let months = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
|
// The first month is January
|
||||||
|
println!("The first month is {}", months[0]);
|
||||||
|
|
||||||
|
// Pretty print
|
||||||
|
println!("{:?}", months);
|
||||||
|
|
||||||
|
// Debug print
|
||||||
|
dbg!(months);
|
||||||
|
|
||||||
|
// You can also initialize an array to contain the same value for each element by specifying the initial value, followed by a semicolon.
|
||||||
|
let array: [i32; 5] = [3; 5]; // same as writing: let array = [3, 3, 3, 3, 3];
|
||||||
|
|
||||||
|
// Invalid Array Element Access
|
||||||
|
// array[6]; // => panic! => program will crash and display a message
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
// Functions are declared using the `fn` keyword.
|
||||||
|
fn a_function() {
|
||||||
|
println!("Another function.");
|
||||||
|
}
|
||||||
|
a_function();
|
||||||
|
|
||||||
|
// Function Parameters
|
||||||
|
// `x` and `y` are called parameters
|
||||||
|
fn another_function(x: i32, y: i32) {
|
||||||
|
println!("The value of x is: {x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// `5` and `6` are called arguments
|
||||||
|
another_function(5, 6);
|
||||||
|
|
||||||
|
// Statements and Expressions
|
||||||
|
// Function bodies are made up of a series of statements optionally ending in an expression.
|
||||||
|
// Rust is an expression-based language.
|
||||||
|
/* Differences:
|
||||||
|
- Statements are instructions that perform some action and do not return a value. (e.g: `let y = 6;`, `function definitions`)
|
||||||
|
- Expressions evaluate to a resulting value. (e.g: `5 + 6` or even the value `6`, calling a function, calling a macro, scope block etc.)
|
||||||
|
|
||||||
|
Expressions can be part of statements.
|
||||||
|
Expressions do not include ending semicolons.
|
||||||
|
=> If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Blocks are expressions too => y = 4
|
||||||
|
let y = {
|
||||||
|
let x = 3;
|
||||||
|
x + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is not possible in Rust, because statements do not return values so there isn't anything for `x` to bind to.
|
||||||
|
// let x = (let y = 6);
|
||||||
|
|
||||||
|
// Functions with Return Values
|
||||||
|
// The return value of the function is the value of the final expression in the block of the body of a function (implicitly).
|
||||||
|
fn five() -> i32 {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
let x = five(); // x = 5
|
||||||
|
|
||||||
|
// You can return early from a function by using the `return` keyword.
|
||||||
|
fn plus_one(x: i32) -> i32 {
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 if number % 2 == 0 {
|
||||||
|
println!("number is divisible by 2.");
|
||||||
|
} else {
|
||||||
|
println!("number is not divisible by 4, 3, or 2.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// `if` in a `let` Statement (like ternary operator)
|
||||||
|
let condition = true;
|
||||||
|
let number = if condition { 5 } else { 6 };
|
||||||
|
|
||||||
|
// `loop` Expression
|
||||||
|
// Loop forever until you explicitly tell it to stop with `break`.
|
||||||
|
loop {
|
||||||
|
println!("again!");
|
||||||
|
if condition {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returning Values from Loops
|
||||||
|
let mut counter = 0;
|
||||||
|
// result = 20
|
||||||
|
let result = loop {
|
||||||
|
counter += 1;
|
||||||
|
|
||||||
|
if counter == 10 {
|
||||||
|
break counter * 2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop Labels to Disambiguate Between Multiple Loops
|
||||||
|
// `break` and `continue` apply to the innermost loop at that point.
|
||||||
|
// You can optionally specify a loop label on a loop that you can then use with break or continue to specify that those keywords apply to the labeled loop instead of the innermost loop.
|
||||||
|
let mut count = 0;
|
||||||
|
'counting_up: loop {
|
||||||
|
println!("count = {count}");
|
||||||
|
let mut remaining = 10;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
println!("remaining = {remaining}");
|
||||||
|
if remaining == 9 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if count == 2 {
|
||||||
|
break 'counting_up;
|
||||||
|
}
|
||||||
|
remaining -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
println!("End count = {count}");
|
||||||
|
|
||||||
|
// `while` Loops
|
||||||
|
let mut number = 3;
|
||||||
|
while number != 0 {
|
||||||
|
println!("{number}!");
|
||||||
|
number -= 1;
|
||||||
|
}
|
||||||
|
println!("LIFTOFF!");
|
||||||
|
|
||||||
|
// `for` Loops
|
||||||
|
|
||||||
|
// Looping Through a Collection
|
||||||
|
let array = [10, 20, 30, 40, 50];
|
||||||
|
for element in array {
|
||||||
|
println!("the value is: {element}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// `for` Loops with Range
|
||||||
|
// `rev()` => reverse the range
|
||||||
|
for number in (1..=3).rev() {
|
||||||
|
println!("{number}!");
|
||||||
|
}
|
||||||
|
println!("LIFTOFF!");
|
||||||
|
|
||||||
|
// Loop from 0 to 4 (5 excluded)
|
||||||
|
for number in 0..5 {
|
||||||
|
println!("{number}"); // 0, 1, 2, 3, 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop from 0 to 5 (5 included)
|
||||||
|
for number in 0..=5 {
|
||||||
|
println!("{number}"); // 0, 1, 2, 3, 4, 5
|
||||||
|
}
|
||||||
|
}
|
7
chapter_4_ownership/Cargo.lock
generated
Normal file
7
chapter_4_ownership/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_4_ownership"
|
||||||
|
version = "1.0.0"
|
6
chapter_4_ownership/Cargo.toml
Normal file
6
chapter_4_ownership/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_4_ownership"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
326
chapter_4_ownership/src/main.rs
Normal file
326
chapter_4_ownership/src/main.rs
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Ownership */
|
||||||
|
// Rust most unique feature that enables memory safety guarantees without needing a garbage collector or without manually allocating and freeing the memory.
|
||||||
|
// Ownership is a set of rules (that the compiler checks) that govern how a Rust program manages memory.
|
||||||
|
// Main purpose of ownership is to manage Heap data.
|
||||||
|
|
||||||
|
/* The Stack and the Heap */
|
||||||
|
// Stack => LIFO (Last In First Out), stores values in the order it gets them and removes the values in the opposite order. Data stored on the stack must have a known, fixed size at compile time. Faster, as it never has to search for a place to put new data or a place to get data from. Variables on the stack are accessed relative to the frame pointer.
|
||||||
|
// Heap => Less organized, stores data of a unknown size at runtime or a size that might change. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location.
|
||||||
|
|
||||||
|
// Basic data types (Scalar Type) like integers, floats, booleans, and characters are stored on the Stack.
|
||||||
|
// Data like strings (Compound Type) that we want to modify or store an unknown size are stored on the Heap.
|
||||||
|
|
||||||
|
// When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function's local variables get pushed onto the stack. When the function is over, those values get popped off the stack (no need to free the memory in the stack, "it's done automatically").
|
||||||
|
|
||||||
|
/* Ownership Rules
|
||||||
|
- Each value in Rust has an owner.
|
||||||
|
- There can only be one owner at a time.
|
||||||
|
- When the owner goes out of scope, the value will be dropped.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Variable Scope */
|
||||||
|
// Scope is the range within a program for which an item is valid.
|
||||||
|
{
|
||||||
|
let string = String::from("Hello, world!");
|
||||||
|
}
|
||||||
|
// `string` variable is not valid here, it's not in scope (and the memory is automatically returned).
|
||||||
|
|
||||||
|
/* String types */
|
||||||
|
// String Literal (`&str`) => Size is known at compile time, immutable, hardcoded into our programs.
|
||||||
|
// String Type (`String`) => Size unknown at compile time (e.g: user input), mutable, stored on the Heap.
|
||||||
|
|
||||||
|
// To create a String Type from a String Literal
|
||||||
|
let mut string = String::from("Hello");
|
||||||
|
// This kind of string can be mutated (given that we used `mut` in the declaration).
|
||||||
|
string.push_str(", world!");
|
||||||
|
string += "abc";
|
||||||
|
string += &String::from("def");
|
||||||
|
|
||||||
|
/* Memory and Allocation */
|
||||||
|
/* In order to support a mutable, growable piece of text with `String`, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents.
|
||||||
|
1. The memory must be requested from the memory allocator at runtime.
|
||||||
|
2. We need a way of returning this memory to the allocator when we're done with our `String` (free it).
|
||||||
|
*/
|
||||||
|
// 1. `String::from` function requests the memory.
|
||||||
|
// 2. The memory is returned once the variable that owns it goes out of scope by calling the `drop` function (automatically called by Rust).
|
||||||
|
|
||||||
|
/* Variables and Data Interacting with Move */
|
||||||
|
// All values that are stored on the Stack (e.g: `i32`)
|
||||||
|
let x = 5;
|
||||||
|
let y = x; // make a copy of the value in `x` and bind it to `y`
|
||||||
|
println!("x = {x}, y = {y}");
|
||||||
|
|
||||||
|
// All values that are stored on the Heap (e.g: `String`)
|
||||||
|
let string1 = String::from("hello");
|
||||||
|
let string2 = string1; // `string1` is moved to `string2` (no copy is made)
|
||||||
|
|
||||||
|
// println!("{string1}"); // `string1` is no longer valid (Rust doesn't need to free anything when `string1` goes out of scope, as it doesn't own anything)
|
||||||
|
println!("{string2}"); // `string2` is valid
|
||||||
|
|
||||||
|
/* String is made up of a group of data stored on the Stack:
|
||||||
|
- Pointer to the memory that holds the contents of the string (stored on the Heap). We store the pointer on the Stack.
|
||||||
|
- Length of the string.
|
||||||
|
- Capacity of the string.
|
||||||
|
*/
|
||||||
|
// When we assign `let string2 = string1;` the pointer (still points to the same location on the Heap, data is not copied, only the pointer is copied.), length, and capacity are copied.
|
||||||
|
|
||||||
|
// When `string1` and `string2` goes out of scope, Rust calls `drop` and the memory on the Heap is freed.
|
||||||
|
// They will both try to free the same memory (double free error) => Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities.
|
||||||
|
// That's why Rust considers `string1` to no longer be valid and Rust won't let us use `string1` after we have moved its value to `string2`.
|
||||||
|
|
||||||
|
// Rust design choice: Rust will never automatically create "deep" copies of your Heap data.
|
||||||
|
|
||||||
|
/* Variables and Data Interacting with Clone */
|
||||||
|
// Deep copy of the Heap data (not just the Stack data liked with move).
|
||||||
|
let string1 = String::from("hello");
|
||||||
|
let string2 = string1.clone(); // Might be expensive (if the data on the Heap is large).
|
||||||
|
println!("string1 = {string1}, string2 = {string2}");
|
||||||
|
|
||||||
|
// TL;DR: Stack data is always copied, Heap data is moved (unless we use `clone`).
|
||||||
|
/*
|
||||||
|
`Copy` annotation trait that we can place on types that are stored on the Stack (except if the type, or any of its parts, has implemented the `Drop` trait).
|
||||||
|
|
||||||
|
If a type implements the `Copy` trait, variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable.
|
||||||
|
|
||||||
|
`Copy` trait are implemented by any group of simple scalar values, and nothing that requires allocation, examples:
|
||||||
|
- All the integer types, such as `u32`.
|
||||||
|
- The Boolean type, `bool`.
|
||||||
|
- All the floating-point types, such as `f64`.
|
||||||
|
- The character type, `char`.
|
||||||
|
- Tuples, if they only contain types that also implement Copy. For example, `(i32, i32)` implements `Copy`, but `(i32, String)` does not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Ownership and Functions */
|
||||||
|
// Passing a variable to a function will move or copy, just as assignment does.
|
||||||
|
|
||||||
|
let string = String::from("hello");
|
||||||
|
takes_ownership(string); // `string` is moved into the function
|
||||||
|
|
||||||
|
// println!("{string}"); // `string` is no longer valid
|
||||||
|
|
||||||
|
let x = 5;
|
||||||
|
makes_copy(x); // `x` is copied into the function
|
||||||
|
|
||||||
|
println!("{x}"); // `x` is still valid
|
||||||
|
|
||||||
|
fn takes_ownership(some_string: String) {
|
||||||
|
println!("{some_string}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makes_copy(some_integer: i32) {
|
||||||
|
println!("{some_integer}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return Values and Scope
|
||||||
|
// Returning values can also transfer ownership.
|
||||||
|
let string1 = gives_ownership(); // `string1` comes into scope
|
||||||
|
|
||||||
|
let string2 = String::from("hello"); // `string2` comes into scope
|
||||||
|
let string3 = takes_and_gives_back(string2); // `string2` is moved into the function (no longer available) and `string3` comes into scope
|
||||||
|
|
||||||
|
fn gives_ownership() -> String {
|
||||||
|
let some_string = String::from("hello");
|
||||||
|
some_string // `some_string` is returned and moves out to the calling function
|
||||||
|
}
|
||||||
|
|
||||||
|
fn takes_and_gives_back(a_string: String) -> String {
|
||||||
|
a_string // `a_string` is returned and moves out to the calling function
|
||||||
|
}
|
||||||
|
|
||||||
|
// TL;DR: The ownership of a variable follows the same pattern every time: assigning a value to another variable moves it. When a variable that includes data on the heap goes out of scope, the value will be cleaned up by `drop` unless ownership of the data has been moved to another variable.
|
||||||
|
|
||||||
|
// What if we want to let a function use a value but not take ownership?
|
||||||
|
// => Rust does let us return multiple values using a tuple.
|
||||||
|
fn calculate_length(string: String) -> (String, usize) {
|
||||||
|
let length = string.len(); // `len()` returns the length of a `String`
|
||||||
|
(string, length)
|
||||||
|
}
|
||||||
|
let string1 = String::from("hello");
|
||||||
|
let (string2, length) = calculate_length(string1);
|
||||||
|
println!("The length of '{string2}' is {length}.");
|
||||||
|
// println!("string1 = {string1}"); // `string1` is no longer valid
|
||||||
|
|
||||||
|
// Problem: Requires lot of work for a concept that should be common.
|
||||||
|
|
||||||
|
/* References and Borrowing (`&`) */
|
||||||
|
/* References => allow you to refer to some value without taking ownership of it:
|
||||||
|
- Address we can follow to access the data stored.
|
||||||
|
- Data is owned by some other variable.
|
||||||
|
- Guaranteed to point to a valid value of a particular type for the life of that reference (unlike pointer).
|
||||||
|
*/
|
||||||
|
// References are immutable by default.
|
||||||
|
// References are reprensented by `&` and `&mut` for mutable references.
|
||||||
|
// Opposite of referencing (`&`) is dereferencing (`*`).
|
||||||
|
fn calculate_length_references(string: &String) -> usize {
|
||||||
|
string.len()
|
||||||
|
}
|
||||||
|
let string1 = String::from("hello");
|
||||||
|
let length = calculate_length_references(&string1);
|
||||||
|
println!("The length of '{string1}' is {length}.");
|
||||||
|
// Creating a reference => Borrowing.
|
||||||
|
// We can't modifiy a variable we're borrowing (immutable by default).
|
||||||
|
|
||||||
|
// Mutable References (`&mut`).
|
||||||
|
fn change(some_string: &mut String) {
|
||||||
|
some_string.push_str(", world");
|
||||||
|
}
|
||||||
|
let mut string = String::from("hello");
|
||||||
|
change(&mut string);
|
||||||
|
|
||||||
|
/* Restriction: Cannot borrow a variable as mutable more than once at a time.
|
||||||
|
|
||||||
|
Reasons for this restriction: prevents data races at compile time, similar to a race condition and happens when these three behaviors occur:
|
||||||
|
- 2 or more pointers access the same data at the same time.
|
||||||
|
- At least one of the pointers is being used to write to the data.
|
||||||
|
- There's no mechanism being used to synchronize access to the data.
|
||||||
|
|
||||||
|
=> Data races cause undefined behavior.
|
||||||
|
*/
|
||||||
|
let result1 = &mut string;
|
||||||
|
let result2 = &mut string; // Error: cannot borrow `string` as mutable a second time
|
||||||
|
|
||||||
|
// println!("{result1}, {result2}");
|
||||||
|
|
||||||
|
// Dangling References
|
||||||
|
// A pointer that references a location in memory that may have been given to someone else. Freeing some memory while preserving a pointer to that memory.
|
||||||
|
// Rust prevents dangling references at compile time.
|
||||||
|
|
||||||
|
// Compiler error:
|
||||||
|
// fn dangling_references() -> &String {
|
||||||
|
// let s = String::from("hello");
|
||||||
|
// &s
|
||||||
|
// }
|
||||||
|
|
||||||
|
// `dangling_references` returns a reference to a `String`, but because the `String` is deallocated when the function ends, the reference would be pointing to garbage. Rust won't compile code with dangling references.
|
||||||
|
|
||||||
|
// Solution: Return the value directly => Ownership is moved out, and nothing is deallocated.
|
||||||
|
fn no_dangling_references() -> String {
|
||||||
|
let s = String::from("hello");
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is valid because `some_string` ownership is moved out of the function and returned to the calling function.
|
||||||
|
fn returns_reference(some_string: &String) -> &String {
|
||||||
|
some_string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slice Type */
|
||||||
|
// Reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership.
|
||||||
|
|
||||||
|
// Example: A function that takes a string of words separated by spaces and returns the first word as a reference.
|
||||||
|
|
||||||
|
// 1st solution example (without slices):
|
||||||
|
// The problem with this solution, is that it returns a new `String` (a copy of the first word) and not a reference to the first word (might be something we want to do, but in the example statement, we wanted a reference).
|
||||||
|
fn first_word(string: &String) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
// `.chars()` and `.as_bytes()` might not do what we want, if we compare with characters that their code binary representation takes number of bits >= 8 (in Extended ASCII and UTF-8)
|
||||||
|
// As in this case, the UTF-8 binary representation takes at least 2 bytes and necessarily is not the same as in ASCII (only 1 byte)
|
||||||
|
// Also, in UTF-8, we can represent the same characters in many ways.
|
||||||
|
// Example: "é" != "é" (combined with 'e' U+0065 and U+0301 the accent itself, and is a string, not a character, as it is 2 characters).
|
||||||
|
// Iteration over grapheme clusters might be better.
|
||||||
|
for (_, character) in string.chars().enumerate() {
|
||||||
|
if character == ' ' {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
result.push(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
let sentence = String::from("Hello world!");
|
||||||
|
let sentence_first_word = first_word(&sentence);
|
||||||
|
println!("Sentence: \"{sentence}\" ; First word: \"{sentence_first_word}\"");
|
||||||
|
|
||||||
|
// 2nd example (without slices):
|
||||||
|
// This function returns the index of the end of the word indicated by a space.
|
||||||
|
fn first_word_2(string: &String) -> usize {
|
||||||
|
for (index, character) in string.chars().enumerate() {
|
||||||
|
if character == ' ' {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string.len()
|
||||||
|
}
|
||||||
|
// Problem the value returned is a separate value from the `String`, there's no guarantee that it will still be valid in the future.
|
||||||
|
let mut string = String::from("hello world");
|
||||||
|
let word = first_word(&string); // word will get the value 5
|
||||||
|
string.clear(); // this empties the String, making it equal to ""
|
||||||
|
|
||||||
|
// `word` still has the value `5` here, but there's no more string that we could meaningfully use the value `5` with. `word` is now totally invalid! `word` is getting out of sync with the data in `string`.
|
||||||
|
|
||||||
|
// String Slices (also works for Arrays, and all sorts of other collections)
|
||||||
|
// Slices ensure memory safety.
|
||||||
|
|
||||||
|
// Range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error.
|
||||||
|
let string = String::from("hello world");
|
||||||
|
let hello = &string[0..5]; // "hello"
|
||||||
|
let world = &string[6..11]; // "world"
|
||||||
|
|
||||||
|
// Range syntax (`..`)
|
||||||
|
// By default it starts at index 0, equivalents:
|
||||||
|
let slice = &string[0..2];
|
||||||
|
let slice = &string[..2];
|
||||||
|
|
||||||
|
// By default it ends at the last index, equivalents:
|
||||||
|
let slice = &string[3..string.len()];
|
||||||
|
let slice = &string[3..];
|
||||||
|
|
||||||
|
// Slice of the entire string
|
||||||
|
let slice = &string[0..string.len()];
|
||||||
|
let slice = &string[..];
|
||||||
|
|
||||||
|
// By default the trailing (right) number is excluded you can include it with `=`
|
||||||
|
let hello = &string[0..=4]; // Index 0 (included) and index 4 (included) => "hello"
|
||||||
|
|
||||||
|
// similar to:
|
||||||
|
let hello = &string[0..5]; // Index 0 (included) and index 5 (excluded) => "hello"
|
||||||
|
|
||||||
|
// 3rd example (with slices):
|
||||||
|
fn first_word_3(string: &str) -> &str {
|
||||||
|
for (index, character) in string.chars().enumerate() {
|
||||||
|
if character == ' ' {
|
||||||
|
return &string[0..index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string
|
||||||
|
}
|
||||||
|
// Now the compiler will ensure the references into the `String` remain valid.
|
||||||
|
let mut string = String::from("hello world");
|
||||||
|
let word = first_word_3(&string);
|
||||||
|
// string.clear(); // Error: Rust disallows the mutable reference in `clear` and the immutable reference in `word` from existing at the same time, and compilation fails.
|
||||||
|
println!("the first word is: {}", word);
|
||||||
|
|
||||||
|
// String Literals as Slices
|
||||||
|
let string = "Hello, world!";
|
||||||
|
// `string` is a String Literal (`&str`) => it's a slice pointing to that specific point of the binary.
|
||||||
|
// `&str` is an immutable reference.
|
||||||
|
// => String Literals are already String Slices.
|
||||||
|
|
||||||
|
// String Slices as Parameters
|
||||||
|
fn first_word_4(string: &str) -> &str {
|
||||||
|
for (index, character) in string.chars().enumerate() {
|
||||||
|
if character == ' ' {
|
||||||
|
return &string[0..index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string
|
||||||
|
}
|
||||||
|
// `&str` allows us to use the same function on both `&String` and `&str` values => deref coercions.
|
||||||
|
// => makes our API more general and useful without losing any functionality:
|
||||||
|
let my_string = String::from("hello world");
|
||||||
|
let word = first_word_4(&my_string[0..6]);
|
||||||
|
let word = first_word_4(&my_string[..]);
|
||||||
|
let word = first_word_4(&my_string);
|
||||||
|
|
||||||
|
let my_string_literal = "hello world";
|
||||||
|
let word = first_word_4(&my_string_literal[0..6]);
|
||||||
|
let word = first_word_4(my_string_literal);
|
||||||
|
let word = first_word_4(my_string_literal);
|
||||||
|
}
|
7
chapter_5_structs/Cargo.lock
generated
Normal file
7
chapter_5_structs/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_5_structs"
|
||||||
|
version = "1.0.0"
|
6
chapter_5_structs/Cargo.toml
Normal file
6
chapter_5_structs/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_5_structs"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
168
chapter_5_structs/src/main.rs
Normal file
168
chapter_5_structs/src/main.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Structs */
|
||||||
|
// Structure Related Data.
|
||||||
|
// Similar to tuples: both hold multiple related values.
|
||||||
|
// Unlike with tuples, in a struct you'll name each piece of data.
|
||||||
|
struct User {
|
||||||
|
email: String,
|
||||||
|
username: String,
|
||||||
|
active: bool,
|
||||||
|
sign_in_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance of that struct by specifying concrete values for each of the fields.
|
||||||
|
let mut user1 = User {
|
||||||
|
email: String::from("someone@example.com"),
|
||||||
|
username: String::from("someusername123"),
|
||||||
|
active: true,
|
||||||
|
sign_in_count: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a specific value
|
||||||
|
println!("{}", user1.email);
|
||||||
|
|
||||||
|
// Set a specific value (only possible if the variable is `mut`)
|
||||||
|
user1.username = String::from("User");
|
||||||
|
|
||||||
|
let username = user1.username.clone();
|
||||||
|
|
||||||
|
// Destructuring
|
||||||
|
let User { email, .. } = user1;
|
||||||
|
|
||||||
|
// Creating an instance of a struct is a expression
|
||||||
|
fn build_user(email: String, username: String) -> User {
|
||||||
|
User {
|
||||||
|
email: email,
|
||||||
|
username, // Shorthand struct initialization
|
||||||
|
active: true,
|
||||||
|
sign_in_count: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating Instances From Other Instances With Struct Update Syntax
|
||||||
|
// => Create a new instance of a struct that includes most of the values from another instance, but changes some.
|
||||||
|
|
||||||
|
// Without update syntax
|
||||||
|
let user2 = User {
|
||||||
|
active: user1.active,
|
||||||
|
username: user1.username.clone(),
|
||||||
|
email: String::from("another@example.com"),
|
||||||
|
sign_in_count: user1.sign_in_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
// With update syntax
|
||||||
|
let user2 = User {
|
||||||
|
email: String::from("another@example.com"),
|
||||||
|
..user1
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Tuple structs */
|
||||||
|
// Using Tuple Structs without Named Fields to Create Different Types.
|
||||||
|
// => Don't have names associated with their fields.
|
||||||
|
// => Tuple structs are useful when you want to give the whole tuple a name and make the tuple a different type from other tuples.
|
||||||
|
struct Color(i32, i32, i32);
|
||||||
|
struct Point(i32, i32, i32);
|
||||||
|
let black = Color(0, 0, 0);
|
||||||
|
let origin = Point(0, 0, 0);
|
||||||
|
|
||||||
|
// Destructuring
|
||||||
|
// let (x, y, z) = origin; // not possible with Tuple Structs
|
||||||
|
// instead, we use:
|
||||||
|
let Point(x, y, z) = origin;
|
||||||
|
|
||||||
|
// Get a element:
|
||||||
|
let x = origin.0;
|
||||||
|
|
||||||
|
/* Example Program using structs */
|
||||||
|
// Calculates the area of a rectangle.
|
||||||
|
|
||||||
|
// By default `struct` does not include functionality to print out debugging information (with `{:?}`).
|
||||||
|
// We have to explicitly opt in with `#[derive(Debug)]`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Rectangle {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're using `&` (immutable borrow) so we borrow the struct rather than take ownership of it. This way, `main` retains its ownership and can continue using it later after the function call.
|
||||||
|
fn calculate_rectangle_area(rectangle: &Rectangle) -> u32 {
|
||||||
|
rectangle.width * rectangle.height
|
||||||
|
}
|
||||||
|
|
||||||
|
let rectangle_example = Rectangle {
|
||||||
|
width: 30,
|
||||||
|
height: 50,
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"The area of the rectangle is {} square pixels.",
|
||||||
|
calculate_rectangle_area(&rectangle_example)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Adding Useful Functionality with Derived Traits like (`#[derive(Debug)]`)
|
||||||
|
// Example: Ability to print an instance of Rectangle.
|
||||||
|
// => The `derive` attribute generates code that will implement a trait with its own default implementation on the type you've annotated.
|
||||||
|
println!("rectangle_example is {:?}", rectangle_example);
|
||||||
|
|
||||||
|
/* Method syntax */
|
||||||
|
// Similar to functions are defined within the context of a struct (or an enum or a trait object).
|
||||||
|
// First parameter is always `self`, which represents the instance of the struct the method is being called on.
|
||||||
|
// `impl`: Implementation block.
|
||||||
|
impl Rectangle {
|
||||||
|
// `&self` is short for `self: &Self` (both are valid)
|
||||||
|
fn area(&self) -> u32 {
|
||||||
|
self.width * self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// `&mut self` to change instance fields
|
||||||
|
fn double_width(&mut self) {
|
||||||
|
self.width *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the second Rectangle can fit completely within `self`.
|
||||||
|
*/
|
||||||
|
fn can_hold(&self, rectangle: &Rectangle) -> bool {
|
||||||
|
self.area() >= rectangle.area()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associated Functions that don't have `self` as first parameter like "static"
|
||||||
|
fn square(size: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"The area of the rectangle is {} square pixels.",
|
||||||
|
rectangle_example.area()
|
||||||
|
);
|
||||||
|
let square = Rectangle::square(5);
|
||||||
|
println!("The area of the square is {} square pixels.", square.area()); // 25
|
||||||
|
|
||||||
|
let rectangle1 = Rectangle {
|
||||||
|
width: 30,
|
||||||
|
height: 50,
|
||||||
|
};
|
||||||
|
let rectangle2 = Rectangle {
|
||||||
|
width: 10,
|
||||||
|
height: 40,
|
||||||
|
};
|
||||||
|
let rectangle3 = Rectangle {
|
||||||
|
width: 60,
|
||||||
|
height: 45,
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"Can rectangle1 hold rectangle2? {}",
|
||||||
|
rectangle1.can_hold(&rectangle2)
|
||||||
|
); // true
|
||||||
|
println!(
|
||||||
|
"Can rectangle1 hold rectangle3? {}",
|
||||||
|
rectangle1.can_hold(&rectangle3)
|
||||||
|
); // false
|
||||||
|
}
|
7
chapter_6_enums/Cargo.lock
generated
Normal file
7
chapter_6_enums/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_6_enums"
|
||||||
|
version = "1.0.0"
|
6
chapter_6_enums/Cargo.toml
Normal file
6
chapter_6_enums/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_6_enums"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
189
chapter_6_enums/src/main.rs
Normal file
189
chapter_6_enums/src/main.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Enums */
|
||||||
|
// Define a type by enumerating its possible variants.
|
||||||
|
|
||||||
|
// Example: Any IP address can be either a version four (IPv4) or a version six address (IPv6), but not both at the same time.
|
||||||
|
enum IpAddressKind {
|
||||||
|
V4,
|
||||||
|
V6,
|
||||||
|
}
|
||||||
|
let four = IpAddressKind::V4;
|
||||||
|
let six = IpAddressKind::V6;
|
||||||
|
fn route(ip_kind: IpAddressKind) {}
|
||||||
|
route(four);
|
||||||
|
route(six);
|
||||||
|
|
||||||
|
// Enums with data directly (like "struct")
|
||||||
|
enum IpAddress {
|
||||||
|
V4(String),
|
||||||
|
V6(String),
|
||||||
|
}
|
||||||
|
let home = IpAddress::V4(String::from("127.0.0.1"));
|
||||||
|
let loopback = IpAddress::V6(String::from("::1"));
|
||||||
|
|
||||||
|
// Advantage to use an `enum` rather than a `struct`, each variant can have different types and associated data.
|
||||||
|
enum IpAddressCustom {
|
||||||
|
V4(u8, u8, u8, u8),
|
||||||
|
V6(String),
|
||||||
|
}
|
||||||
|
let home = IpAddressCustom::V4(127, 0, 0, 1);
|
||||||
|
let loopback = IpAddressCustom::V6(String::from("::1"));
|
||||||
|
|
||||||
|
// Storing IP addresses is only a exemple, and as it turns out doing so is common, it's included in the standard library (`std::net::IpAddr`).
|
||||||
|
|
||||||
|
// Another example of an enum:
|
||||||
|
enum Message {
|
||||||
|
Quit,
|
||||||
|
Move { x: i32, y: i32 },
|
||||||
|
Write(String),
|
||||||
|
ChangeColor(i32, i32, i32),
|
||||||
|
}
|
||||||
|
// You can put any kind of data inside an enum variant: strings, numeric types, or structs, for example. You can even include another enum!
|
||||||
|
|
||||||
|
// We can also implements methods on `enum` like for `struct` with `impl`.
|
||||||
|
impl Message {
|
||||||
|
fn call(&self) {
|
||||||
|
// method body would be defined here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let message = Message::Write(String::from("hello"));
|
||||||
|
message.call();
|
||||||
|
|
||||||
|
// The `Option` Enum and Its Advantages Over Null Values
|
||||||
|
// Value could be something or it could be nothing.
|
||||||
|
// Defined by the standard library.
|
||||||
|
// Included in the prelude (don't need to bring it into scope explicitly).
|
||||||
|
// Rust doesn't have the null feature that many other languages have. Null is a value that means there is no value there. In languages with null, variables can always be in one of two states: null or not-null.
|
||||||
|
// Variants: `Some` and `None` (already in prelude, no need to use `Option::` prefix but both work).
|
||||||
|
let some_number = Some(5);
|
||||||
|
let some_char = Some('e');
|
||||||
|
let absent_number: Option<i32> = None;
|
||||||
|
|
||||||
|
// `Option<T>` and `T` are different types.
|
||||||
|
// `i8` is always a valid value, but `Option<i8>` not necessarily as it could be `None`.
|
||||||
|
let x: i8 = 5;
|
||||||
|
let y: Option<i8> = Some(5);
|
||||||
|
|
||||||
|
// This won't compile:
|
||||||
|
// let sum = x + y;
|
||||||
|
|
||||||
|
// We have to convert `Option<T>` to `T` before performing `T` operations => making sure the value is valid.
|
||||||
|
match y {
|
||||||
|
Some(y) => {
|
||||||
|
// `y` is `Some` and has a value
|
||||||
|
let sum = x + y;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// `y` is `None` and doesn't have a value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The `match` Control Flow Construct */
|
||||||
|
// Compare a value against a series of patterns.
|
||||||
|
// Execute code based on which pattern matches.
|
||||||
|
// Patterns can be made up of literal values, variable names, wildcards, and many other things.
|
||||||
|
// `match` is an expression, it returns a value.
|
||||||
|
// => The compiler ensures that all possible cases are handled.
|
||||||
|
|
||||||
|
// Example: Function that takes an unknown United States coin and returns its value in cents.
|
||||||
|
enum Coin {
|
||||||
|
Penny,
|
||||||
|
Nickel,
|
||||||
|
Dime,
|
||||||
|
Quarter,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_in_cents(coin: Coin) -> u8 {
|
||||||
|
// We must handle all the enum variants, otherwise it won't compile.
|
||||||
|
// => Matches Are Exhaustive.
|
||||||
|
match coin {
|
||||||
|
// We can use `return` and `{}` or directly the value.
|
||||||
|
Coin::Penny => {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Coin::Nickel => 5,
|
||||||
|
Coin::Dime => 10,
|
||||||
|
Coin::Quarter => 25,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patterns that Bind to Values
|
||||||
|
// Extract values out of enum variants.
|
||||||
|
|
||||||
|
// Example: Quarter United States Coin minted quarters with different designs for each of the 50 states on one side.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum UsState {
|
||||||
|
Alabama,
|
||||||
|
Alaska,
|
||||||
|
}
|
||||||
|
enum CoinWithState {
|
||||||
|
Penny,
|
||||||
|
Nickel,
|
||||||
|
Dime,
|
||||||
|
Quarter(UsState),
|
||||||
|
}
|
||||||
|
fn value_in_cents_2(coin: CoinWithState) -> u8 {
|
||||||
|
match coin {
|
||||||
|
CoinWithState::Penny => 1,
|
||||||
|
CoinWithState::Nickel => 5,
|
||||||
|
CoinWithState::Dime => 10,
|
||||||
|
// We can use `state` to get the value of the variant.
|
||||||
|
CoinWithState::Quarter(state) => {
|
||||||
|
println!("State quarter from {:?}!", state);
|
||||||
|
25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching with `Option<T>`
|
||||||
|
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||||
|
match x {
|
||||||
|
None => None,
|
||||||
|
Some(x) => Some(x + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let five = Some(5);
|
||||||
|
let six = plus_one(five); // `Some(6)`
|
||||||
|
let none = plus_one(None); // `None`
|
||||||
|
|
||||||
|
// Catch-all Patterns and the `_` Placeholder
|
||||||
|
// Using enums, we can also take special actions for a few particular values, but for all other values take one default action.
|
||||||
|
|
||||||
|
// Example: Game where, if you roll a 3 on a dice roll, your player doesn't move, but instead gets a new fancy hat. If you roll a 7, your player loses a fancy hat. For all other values, your player moves that number of spaces on the game board.
|
||||||
|
let dice_roll = 9;
|
||||||
|
match dice_roll {
|
||||||
|
3 => add_fancy_hat(),
|
||||||
|
7 => remove_fancy_hat(),
|
||||||
|
|
||||||
|
// It matches all possible values, we can call the variable however we want, here `other` and if we don't need it we can use `_`.
|
||||||
|
other => move_player(other),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_fancy_hat() {}
|
||||||
|
fn remove_fancy_hat() {}
|
||||||
|
fn move_player(spaces: u8) {}
|
||||||
|
|
||||||
|
// Concise Control Flow with `if let`
|
||||||
|
// Lets you combine `if` and `let` into a less verbose way to handle values that match one pattern while ignoring the rest.
|
||||||
|
// `if let` as "syntax sugar" for a `match` that runs code when the value matches one pattern and then ignores all other values.
|
||||||
|
|
||||||
|
// Example: We want to only execute code if the value is the `Some` variant.
|
||||||
|
|
||||||
|
// With `match`:
|
||||||
|
let value = Some(5);
|
||||||
|
match value {
|
||||||
|
Some(value) => println!("The value is {}", value),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// With `if let` (less boilerplate code to add):
|
||||||
|
let value = Some(5);
|
||||||
|
if let Some(value) = value {
|
||||||
|
println!("The value is {}", value);
|
||||||
|
}
|
||||||
|
}
|
7
chapter_7_packages_crates_modules/Cargo.lock
generated
Normal file
7
chapter_7_packages_crates_modules/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_7_packages_crates_modules"
|
||||||
|
version = "1.0.0"
|
6
chapter_7_packages_crates_modules/Cargo.toml
Normal file
6
chapter_7_packages_crates_modules/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_7_packages_crates_modules"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
159
chapter_7_packages_crates_modules/README.md
Normal file
159
chapter_7_packages_crates_modules/README.md
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
# 7. Managing Growing Projects with Packages, Crates, and Modules
|
||||||
|
|
||||||
|
=> Organizing the code.
|
||||||
|
=> Packages can contain multiple binary crates and optionally one library crate.
|
||||||
|
=> Very large projects can use workspaces.
|
||||||
|
|
||||||
|
These features, sometimes collectively referred to as the module system, include:
|
||||||
|
|
||||||
|
- **Packages:** A Cargo feature that lets you build, test, and share crates.
|
||||||
|
- **Crates:** A tree of modules that produces a library (`--lib`) or executable (`--bin`).
|
||||||
|
- **Modules and use:** Let you control the organization, scope, and privacy of paths.
|
||||||
|
- **Paths:** A way of naming an item, such as a struct, function, or module.
|
||||||
|
|
||||||
|
When we execute `cargo new`, Cargo creates a `Cargo.toml` file giving us a package, there's also a `src` directory, with the crate root file, following the convention (by default):
|
||||||
|
|
||||||
|
- Binary crates have a `src/main.rs` root file.
|
||||||
|
- Library crates have a `src/lib.rs` root file.
|
||||||
|
|
||||||
|
Crate root files create a module named `crate` at the root of the crate's module structure, known as the module tree.
|
||||||
|
|
||||||
|
If we have both files, it means that our package contains both a library and a binary crate.
|
||||||
|
|
||||||
|
A package can have multiple binary crates by placing files in the `src/bin` directory (additionally to `src/main.rs`), but only one library crate `src/lib.rs`.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
In the crate root file, we can declare modules with the `mod` keyword (e.g: `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 { // code }`.
|
||||||
|
- In the file `src/garden.rs`.
|
||||||
|
- In the file `src/garden/mod.rs`.
|
||||||
|
|
||||||
|
=> Code within a module is private by default, we must use `pub` for each code we're exporting.
|
||||||
|
|
||||||
|
## 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, within curly brackets that replace the semicolon following `mod vegetables { // code }`.
|
||||||
|
- 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`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod front_of_house {
|
||||||
|
pub mod hosting {
|
||||||
|
pub fn add_to_waitlist() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eat_at_restaurant() {
|
||||||
|
// Absolute path
|
||||||
|
crate::front_of_house::hosting::add_to_waitlist();
|
||||||
|
|
||||||
|
// Relative path
|
||||||
|
front_of_house::hosting::add_to_waitlist();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=> Our preference in general is to **specify absolute paths** because it's more likely we'll want to move code definitions and item calls independently of each other.
|
||||||
|
|
||||||
|
## 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 to `crate::garden::vegetables::Asparagus`, you can create a shortcut with `use crate::garden::vegetables::Asparagus;` and from then on you only need to write `Asparagus` to make use of that type in the scope.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod front_of_house {
|
||||||
|
pub mod hosting {
|
||||||
|
pub fn add_to_waitlist() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::front_of_house::hosting;
|
||||||
|
|
||||||
|
pub fn eat_at_restaurant() {
|
||||||
|
hosting::add_to_waitlist();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Conventions:
|
||||||
|
|
||||||
|
- For functions, we use until the last part of the path (without the the function name), so he have still to call the module name (e.g: `hosting::add_to_waitlist();` and not `add_to_waitlist();` directly).
|
||||||
|
- For structs, enums, and other items with `use`, we use the full path (except, if we're bringing 2 items with the same name):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(1, 2);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also reexport items with `pub use`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod front_of_house {
|
||||||
|
pub mod hosting {
|
||||||
|
pub fn add_to_waitlist() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use crate::front_of_house::hosting;
|
||||||
|
|
||||||
|
pub fn eat_at_restaurant() {
|
||||||
|
hosting::add_to_waitlist();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also use nested path if we're using multiple items defined in the module:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Without nesting
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
// With nesting
|
||||||
|
use std::{cmp::Ordering, io};
|
||||||
|
```
|
||||||
|
|
||||||
|
Another example of nesting:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Without nesting
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
// With nesting
|
||||||
|
use std::io::{self, Write};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Declaration of modules and submodules in the same file
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod front_of_house {
|
||||||
|
mod hosting {
|
||||||
|
fn add_to_waitlist() {}
|
||||||
|
|
||||||
|
fn seat_at_table() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod serving {
|
||||||
|
fn take_order() {}
|
||||||
|
|
||||||
|
fn serve_order() {}
|
||||||
|
|
||||||
|
fn take_payment() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
1
chapter_7_packages_crates_modules/src/garden/mod.rs
Normal file
1
chapter_7_packages_crates_modules/src/garden/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod vegetables;
|
@ -0,0 +1,2 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Asparagus {}
|
9
chapter_7_packages_crates_modules/src/main.rs
Normal file
9
chapter_7_packages_crates_modules/src/main.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use crate::garden::vegetables::Asparagus;
|
||||||
|
|
||||||
|
// Tells the compiler to include the code it finds in `src/garden/mod.rs`.
|
||||||
|
pub mod garden;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let plant = Asparagus {};
|
||||||
|
println!("I'm growing {:?}!", plant);
|
||||||
|
}
|
7
chapter_8_common_collections/Cargo.lock
generated
Normal file
7
chapter_8_common_collections/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_8_common_collections"
|
||||||
|
version = "1.0.0"
|
6
chapter_8_common_collections/Cargo.toml
Normal file
6
chapter_8_common_collections/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_8_common_collections"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
74
chapter_8_common_collections/src/hash_maps.rs
Normal file
74
chapter_8_common_collections/src/hash_maps.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
|
||||||
|
pub fn main_hash_maps() {
|
||||||
|
/* Hash Maps (`HashMap<K, V>`) */
|
||||||
|
// Stores a mapping of keys of type `K` to values of type `V` using a hash function (like a dictionary).
|
||||||
|
|
||||||
|
// Creating a New Hash Map
|
||||||
|
let mut scores: HashMap<String, i32> = HashMap::new();
|
||||||
|
scores.insert(String::from("Blue"), 10);
|
||||||
|
scores.insert(String::from("Yellow"), 50);
|
||||||
|
|
||||||
|
// Accessing Values in a Hash Map
|
||||||
|
let team_name = String::from("Blue");
|
||||||
|
|
||||||
|
// `get` returns an `Option<&V>` (a reference to the value of type `V`).
|
||||||
|
// We can use `.copied` after `.get` to get a copy of the value instead of a reference so we get `Option<V>` instead of `Option<&V>`.
|
||||||
|
// match scores.get(&team_name).copied() {
|
||||||
|
match scores.get(&team_name) {
|
||||||
|
Some(score) => println!("{team_name}: {score}."),
|
||||||
|
None => println!("The team {team_name} does not exist."),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another way to handle `Option` returned by `get`:
|
||||||
|
// `unwrap_or` returns the value inside the `Some` variant if it exists, otherwise it returns the value passed as an argument.
|
||||||
|
let score = scores.get(&team_name).copied().unwrap_or(0);
|
||||||
|
|
||||||
|
// Iterating Over Each Key-Value Pair in a Hash Map (arbitrary order)
|
||||||
|
for (key, value) in &scores {
|
||||||
|
println!("{key}: {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash Maps and Ownership
|
||||||
|
// For types that implement the `Copy` trait, like `i32`, the values are copied into the Hash Map.
|
||||||
|
// For owned values like `String`, the values will be moved and the Hash Map will be the owner of those values.
|
||||||
|
|
||||||
|
let field_name = String::from("Favorite color");
|
||||||
|
let field_value = String::from("Blue");
|
||||||
|
|
||||||
|
let mut map: HashMap<String, String> = HashMap::new();
|
||||||
|
map.insert(field_name, field_value);
|
||||||
|
// field_name and field_value are invalid at this point.
|
||||||
|
// println!("{field_name}: {field_value}"); // error: use of moved values: `field_name`, `field_value`
|
||||||
|
|
||||||
|
// Overwriting a Value
|
||||||
|
// If we insert a key and a value into a Mash Map and then insert that same key with a different value, the value associated with that key will be replaced.
|
||||||
|
let mut scores: HashMap<String, i32> = HashMap::new();
|
||||||
|
scores.insert(String::from("Blue"), 25);
|
||||||
|
scores.insert(String::from("Blue"), 50);
|
||||||
|
println!("{:?}", scores); // {"Blue": 50}
|
||||||
|
|
||||||
|
// Only Inserting a Value If the Key Has No Value
|
||||||
|
let mut scores = HashMap::new();
|
||||||
|
scores.insert(String::from("Blue"), 10);
|
||||||
|
|
||||||
|
scores.entry(String::from("Yellow")).or_insert(50);
|
||||||
|
scores.entry(String::from("Blue")).or_insert(50);
|
||||||
|
println!("{:?}", scores); // {"Blue": 10, "Yellow": 50}
|
||||||
|
|
||||||
|
// Updating a Value Based on the Old Value
|
||||||
|
// Example: Counting how many times each word appears in some text.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
println!("{:?}", map); // {"hello": 1, "world": 2, "wonderful": 1}
|
||||||
|
}
|
14
chapter_8_common_collections/src/main.rs
Normal file
14
chapter_8_common_collections/src/main.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
pub mod hash_maps;
|
||||||
|
pub mod strings;
|
||||||
|
pub mod vectors;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Common Collections */
|
||||||
|
// Rust's standard library includes a number of very useful data structures called collections.
|
||||||
|
// Unlike the built-in array and tuple types, the data these collections point to is stored on the heap, which means the amount of data does not need to be known at compile time and can grow or shrink as the program runs.
|
||||||
|
|
||||||
|
// We will learn about 3 collections in this chapter: `hash_maps.rs`, `strings.rs`, `vectors.rs`.
|
||||||
|
hash_maps::main_hash_maps();
|
||||||
|
strings::main_strings();
|
||||||
|
vectors::main_vectors();
|
||||||
|
}
|
124
chapter_8_common_collections/src/strings.rs
Normal file
124
chapter_8_common_collections/src/strings.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
|
||||||
|
pub fn main_strings() {
|
||||||
|
/* Strings (`String`) */
|
||||||
|
// Collection of bytes.
|
||||||
|
// Wrapper around `Vec<u8>` (vector of `u8` values).
|
||||||
|
// UTF-8 encoded.
|
||||||
|
// String Literal (`&str`) => Size is known at compile time, immutable, hardcoded into our programs.
|
||||||
|
// String Type (`String`) => Size unknown at compile time (e.g: user input), mutable, stored on the Heap.
|
||||||
|
|
||||||
|
// Creating a New String
|
||||||
|
let mut string = String::new(); // empty string
|
||||||
|
let mut string = "initial contents".to_string();
|
||||||
|
let mut string = String::from("initial contents");
|
||||||
|
|
||||||
|
// Updating a String
|
||||||
|
let mut string = String::from("foo");
|
||||||
|
string.push_str("bar");
|
||||||
|
string.push('a'); // push a single character
|
||||||
|
|
||||||
|
// Concatenation with the `+` Operator
|
||||||
|
let string1 = String::from("Hello, ");
|
||||||
|
let string2 = String::from("world!");
|
||||||
|
let string3 = string1 + &string2; // note `string1` has been moved here and can no longer be used
|
||||||
|
|
||||||
|
// The `+` operator uses the `add` method:
|
||||||
|
let string4 = string3.add("concatenated string");
|
||||||
|
|
||||||
|
// `format!` Macro (works like `println!` but returns a `String` instead of printing to the console)
|
||||||
|
let tic = String::from("tic");
|
||||||
|
let tac = String::from("tac");
|
||||||
|
let toe = String::from("toe");
|
||||||
|
let result = format!("{tic}-{tac}-{toe}");
|
||||||
|
|
||||||
|
// Indexing into Strings
|
||||||
|
// In many other programming languages, accessing individual characters in a string by referencing them by index is a valid and common operation.
|
||||||
|
// However, if you try to access parts of a `String` using indexing syntax in Rust, you'll get an error.
|
||||||
|
|
||||||
|
// Rust strings don't support indexing.
|
||||||
|
// The reason is that a string is a wrapper over a `Vec<u8>`, that means, we would access a byte, not necessarily a character as UTF-8 is a variable width encoding (from 1 to 4 bytes).
|
||||||
|
// Therefore, an index into the string's bytes will not always correlate to a valid Unicode scalar value.
|
||||||
|
|
||||||
|
// Bytes and Scalar Values and Grapheme Clusters
|
||||||
|
// 3 ways to look at strings: as bytes, scalar values, and grapheme clusters (the closest thing to what we would call letters).
|
||||||
|
|
||||||
|
// Example: "é" != "é" (it is combined with 'e' U+0065 and U+0301 the accent itself, and is a string, not a character, as it is 2 characters).
|
||||||
|
// Looking at "é" (the string "e" followed by the accent)
|
||||||
|
let string = String::from("é");
|
||||||
|
|
||||||
|
// - As bytes (`u8`): [101, 204, 129]
|
||||||
|
let bytes = string.as_bytes();
|
||||||
|
|
||||||
|
// - As scalar values (`char`): ['e', '́']
|
||||||
|
let chars = string.chars();
|
||||||
|
|
||||||
|
// - As grapheme clusters: ['é']
|
||||||
|
// Not implemented by the Rust Standard Library (as it is complex) but is implemented with crates on crates.io (e.g: `unicode-segmentation`).
|
||||||
|
|
||||||
|
// Usually we want to look at strings as grapheme clusters, which means we want to take into account the way people perceive the characters.
|
||||||
|
|
||||||
|
// Slicing Strings
|
||||||
|
// We can use `[]` with a range to create a string slice containing particular bytes:
|
||||||
|
let string = String::from("Hello, world!");
|
||||||
|
let hello = &string[0..5]; // Takes the first 5 bytes
|
||||||
|
|
||||||
|
// let h = &string[0..1]; // panic at runtime
|
||||||
|
|
||||||
|
// Methods for Iterating Over Strings
|
||||||
|
// => Be explicit about what you want to do with the characters (bytes? chars? grapheme clusters?).
|
||||||
|
|
||||||
|
for character in "hello".chars() {
|
||||||
|
println!("{character}"); // => "h" "e" "l" "l" "o"
|
||||||
|
}
|
||||||
|
|
||||||
|
for byte in "hello".bytes() {
|
||||||
|
println!("{} => {}", byte, byte as char); // "104 => h" "101 => e" "108 => l" "108 => l" "111 => o
|
||||||
|
}
|
||||||
|
|
||||||
|
// Useful methods:
|
||||||
|
println!("Length in bytes: {}", "hello".len()); // => 5
|
||||||
|
println!("Length in chars: {}", "hello".chars().count()); // => 5
|
||||||
|
|
||||||
|
// Length in grapheme clusters: not implemented by the Rust Standard Library.
|
||||||
|
|
||||||
|
println!("Is empty: {}", "".is_empty()); // => true
|
||||||
|
println!("Is empty: {}", "hello".is_empty()); // => false
|
||||||
|
|
||||||
|
println!("Contains \"ello\": {}", "hello".contains("ello")); // => true
|
||||||
|
println!("Contains \"abc\": {}", "hello".contains("abc")); // => false
|
||||||
|
|
||||||
|
println!("Starts with \"hello\": {}", "hello".starts_with("hello")); // => true
|
||||||
|
println!("Starts with \"abc\": {}", "hello".starts_with("abc")); // => false
|
||||||
|
|
||||||
|
// We can also add methods to strings (thanks to `trait`, ref: Chapter 10)
|
||||||
|
// Example: Capitalize a string ("capitalize a string" => "Capitalize a string")
|
||||||
|
fn capitalize(string: &str) -> String {
|
||||||
|
let mut characters = string.chars();
|
||||||
|
match characters.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(first) => first.to_uppercase().collect::<String>() + characters.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Capitalize {
|
||||||
|
fn capitalize(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Capitalize for str {
|
||||||
|
fn capitalize(&self) -> String {
|
||||||
|
capitalize(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = String::from("capitalize 😎");
|
||||||
|
println!("capitalize: {}", capitalize(&string));
|
||||||
|
println!("capitalize: {}", string.capitalize());
|
||||||
|
println!("capitalize: {}", string);
|
||||||
|
}
|
73
chapter_8_common_collections/src/vectors.rs
Normal file
73
chapter_8_common_collections/src/vectors.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
|
||||||
|
pub fn main_vectors() {
|
||||||
|
/* Vectors (`Vet<T>`) */
|
||||||
|
// Storing Lists of Values.
|
||||||
|
|
||||||
|
// Creating a New Vector
|
||||||
|
let _vector: Vec<i32> = Vec::new(); // empty vector
|
||||||
|
let mut vector = vec![1, 2, 3]; // vector with values
|
||||||
|
vector.push(4); // add value to vector (given that vector is mutable `mut`)
|
||||||
|
|
||||||
|
// Reading Elements of Vectors
|
||||||
|
let third: &i32 = &vector[2]; // will panic if index is out of bounds
|
||||||
|
println!("The third element is {third}.");
|
||||||
|
|
||||||
|
// Safer Access with `get` (handle index out of bounds explicitly)
|
||||||
|
let third: Option<&i32> = vector.get(2);
|
||||||
|
match third {
|
||||||
|
Some(third) => {
|
||||||
|
println!("The third element is {third}.");
|
||||||
|
}
|
||||||
|
None => println!("There is no third element."),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrow checker rule
|
||||||
|
let mut vector = vec![1, 2, 3, 4, 5];
|
||||||
|
let first = &vector[0];
|
||||||
|
// vector.push(6); // will cause error: cannot borrow `vector` as mutable because it is also borrowed as immutable
|
||||||
|
println!("The first element is: {first}");
|
||||||
|
// => We can't have mutable and immutable references in the same scope.
|
||||||
|
// That rule applies here, we hold an immutable reference to the first element in a vector and try to add an element to the end.
|
||||||
|
// This error is due to the way vectors work: because vectors put the values next to each other in memory, adding a new element onto the end of the vector might require allocating new memory and copying the old elements to the new space, if there isn't enough room to put all the elements next to each other where the vector is currently stored. In that case, the reference to the first element would be pointing to deallocated memory.
|
||||||
|
// => Instead we can use the `get` method to get a reference to an element without holding a reference to the whole vector.
|
||||||
|
|
||||||
|
// Iterating over the Values in a Vector
|
||||||
|
let vector = vec![100, 32, 57];
|
||||||
|
for item in &vector {
|
||||||
|
println!("{item}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can also iterate over mutable references to each element in a mutable vector in order to make changes to all the elements.
|
||||||
|
let mut vector = vec![100, 32, 57];
|
||||||
|
for item in &mut vector {
|
||||||
|
*item += 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterating over a vector, whether immutably or mutably, is safe.
|
||||||
|
// If we try `vector.push(4);` in the loop, we'll get a compile-time error (borrow checker rule).
|
||||||
|
|
||||||
|
// Using an Enum to Store Multiple Types
|
||||||
|
enum SpreadsheetCell {
|
||||||
|
Int(i32),
|
||||||
|
Float(f64),
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
let row = vec![
|
||||||
|
SpreadsheetCell::Int(3),
|
||||||
|
SpreadsheetCell::Text(String::from("blue")),
|
||||||
|
SpreadsheetCell::Float(10.12),
|
||||||
|
];
|
||||||
|
match row.get(0) {
|
||||||
|
Some(value) => match value {
|
||||||
|
SpreadsheetCell::Int(value) => {}
|
||||||
|
SpreadsheetCell::Float(value) => {}
|
||||||
|
SpreadsheetCell::Text(value) => {}
|
||||||
|
},
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
}
|
7
chapter_9_error_handling/Cargo.lock
generated
Normal file
7
chapter_9_error_handling/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chapter_9_error_handling"
|
||||||
|
version = "1.0.0"
|
6
chapter_9_error_handling/Cargo.toml
Normal file
6
chapter_9_error_handling/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "chapter_9_error_handling"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
145
chapter_9_error_handling/src/main.rs
Normal file
145
chapter_9_error_handling/src/main.rs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::{self, ErrorKind, Read};
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
// Disable warnings for learning purposes and avoid useless `println!` usage only to use a variable
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/* Error Handling */
|
||||||
|
|
||||||
|
/* 2 categories:
|
||||||
|
1. `Result<T, E>`: Recoverable Errors (e.g: file not found, invalid input, etc.) => Report the error to the user and retry the operation.
|
||||||
|
2. `panic!`: Unrecoverable Errors (e.g: index out of bounds, etc.) => Symptoms of bugs, we want to immediately stop the program.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Unrecoverable Errors with `panic!` */
|
||||||
|
// Sometimes, bad things happen in your code, and there's nothing you can do about it.
|
||||||
|
// By default, these panics will print a failure message, unwind, clean up the stack, and quit.
|
||||||
|
// We can also display the call stack of the error by setting the `RUST_BACKTRACE` environment variable to `1`.
|
||||||
|
panic!("crash and burn");
|
||||||
|
|
||||||
|
// `panic!` call coming from a library
|
||||||
|
let vector = vec![1, 2, 3];
|
||||||
|
vector[99];
|
||||||
|
// We're attempting to access the 100th element of our vector but the vector has only 3 elements.
|
||||||
|
// Attempting to read beyond the end of a data structure is undefined behavior.
|
||||||
|
// You might get whatever is at the location in memory that would correspond to that element in the data structure, even though the memory doesn't belong to that structure (buffer overread). It leads to security vulnerabilities.
|
||||||
|
// Rust will `panic!` to protect you from this sort of vulnerability.
|
||||||
|
|
||||||
|
/* Recoverable Errors with `Result<T, E>` */
|
||||||
|
// Most errors aren't serious enough to require the program to stop entirely.
|
||||||
|
// Sometimes, when a function fails, it's for a reason that you can easily interpret and respond to.
|
||||||
|
// Example: If you try to open a file and that operation fails because the file doesn't exist, you might want to create the file instead of terminating the process.
|
||||||
|
// Variants: `Ok` and `Err`
|
||||||
|
let greeting_file_result = File::open("hello.txt");
|
||||||
|
|
||||||
|
let greeting_file = match greeting_file_result {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(error) => {
|
||||||
|
panic!("Problem opening the file: {:?}", error);
|
||||||
|
File::create("hello.txt").unwrap_or_else(|error| {
|
||||||
|
panic!("Problem creating the file: {:?}", error);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Matching on Different Errors (using `error.kind()`)
|
||||||
|
// Example: We want to create the file if it doesn't exist, but `panic!` if we get any other error (e.g: we didn't have permission to open the file).
|
||||||
|
let greeting_file = match greeting_file_result {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(error) => match error.kind() {
|
||||||
|
ErrorKind::NotFound => match File::create("hello.txt") {
|
||||||
|
Ok(file_created) => file_created,
|
||||||
|
Err(error) => panic!("Problem creating the file: {:?}", error),
|
||||||
|
},
|
||||||
|
other_error => {
|
||||||
|
panic!("Problem opening the file: {:?}", other_error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Alternatives to Using `match` with `Result<T, E>`
|
||||||
|
// Sometimes, there are lot of `match` statements to handle `Result<T, E>` and it can be hard to read.
|
||||||
|
// There are many other methods available on `Result<T, E>` that make it easier to handle the different variants (e.g `.unwrap`, `.expect`, `.unwrap_or_else`, etc.).
|
||||||
|
|
||||||
|
// Shortcuts for Panic on Error: `.unwrap` and `.expect` (`Result<T, E>` helper methods)
|
||||||
|
|
||||||
|
// `.unwrap` will `panic!` if the value of the `Result<T, E>` is an `Err` variant or otherwise return the `Ok` value.
|
||||||
|
let greeting_file = File::open("hello.txt").unwrap();
|
||||||
|
|
||||||
|
// `.expect` is similar to `.unwrap` but we can also provide a custom panic message.
|
||||||
|
let greeting_file = File::open("hello.txt").expect("Failed to open `hello.txt`.");
|
||||||
|
|
||||||
|
/* Propagating Errors */
|
||||||
|
// When a function's implementation calls something that might fail.
|
||||||
|
// Instead of handling the error within the function itself, we can return the error to the calling code so that it can decide what to do.
|
||||||
|
|
||||||
|
// Example: Function that reads a username from a file. If the file doesn't exist or can't be read, this function will return those errors to the code that called the function.
|
||||||
|
fn read_username_from_file() -> Result<String, io::Error> {
|
||||||
|
let username_file_result = File::open("hello.txt");
|
||||||
|
let mut username_file = match username_file_result {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
let mut username = String::new();
|
||||||
|
match username_file.read_to_string(&mut username) {
|
||||||
|
Ok(_) => Ok(username),
|
||||||
|
Err(error) => Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcuts for Propagating Errors: `?` Operator
|
||||||
|
// `?`: If the value of the `Result` is an `Ok`, the value inside the `Ok` will get returned from this expression, and the program will continue. If the value is an `Err`, the `Err` will be returned.
|
||||||
|
// `?` can only be used in functions that return `Result` or `Option`.
|
||||||
|
fn read_username_from_file_shorter() -> Result<String, io::Error> {
|
||||||
|
let mut username_file = File::open("hello.txt")?;
|
||||||
|
let mut username = String::new();
|
||||||
|
username_file.read_to_string(&mut username)?;
|
||||||
|
Ok(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `?` operator eliminates a lot of boilerplate and makes this function's implementation simpler.
|
||||||
|
// Example: `?` chaining
|
||||||
|
fn read_username_from_file_even_shorter() -> Result<String, io::Error> {
|
||||||
|
let mut username = String::new();
|
||||||
|
File::open("hello.txt")?.read_to_string(&mut username)?;
|
||||||
|
Ok(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading a file into a string is a fairly common operation so the standard library provides `fs::read_to_string` to do this in one line.
|
||||||
|
fn read_username_from_file_one_liner() -> Result<String, io::Error> {
|
||||||
|
fs::read_to_string("hello.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// `?` also works with `Option<T>` values.
|
||||||
|
fn last_char_of_first_line(text: &str) -> Option<char> {
|
||||||
|
text.lines().next()?.chars().last()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To `panic!` or Not to `panic!` */
|
||||||
|
// Good default choice: `Result<T, E>`.
|
||||||
|
// => It gives the calling code options to decide how to handle the error (recover or call `panic!` themselves).
|
||||||
|
|
||||||
|
// Examples, Prototype Code, and Tests
|
||||||
|
// => `panic!` is appropriate.
|
||||||
|
|
||||||
|
// - Example: When you're writing an example to illustrate some concept, it's understood that a call to a method like `.unwrap` that could panic is meant as a placeholder for the way you'd want your application to handle errors.
|
||||||
|
|
||||||
|
// - Prototype: `.unwrap` and `.expect` methods are very handy when prototyping, before you're ready to decide how to handle errors.
|
||||||
|
|
||||||
|
// - Tests: If a method call fails in a test, you'd want the whole test to fail, `panic!` is how a test is marked as a failure, calling `.unwrap` or `.expect` is exactly what should happen.
|
||||||
|
|
||||||
|
// Similarly, `panic!` is often appropriate if you're calling external code that is out of your control and it returns an invalid state that you have no way of fixing.
|
||||||
|
|
||||||
|
// Cases in Which You Have More Information Than the Compiler
|
||||||
|
// It would also be appropriate to call `.unwrap` or `.expect` when you have some other logic that ensures the `Result` will have an `Ok` value, but the logic isn't something the compiler understands.
|
||||||
|
// Example:
|
||||||
|
let home: IpAddr = "127.0.0.1"
|
||||||
|
.parse()
|
||||||
|
.expect("Hardcoded IP address should be valid");
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user