Initial commit: start => 6.1

This commit is contained in:
Zykino 2018-10-16 21:06:19 +02:00
commit 5944ccfce4
4 changed files with 667 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

4
Cargo.lock generated Normal file
View File

@ -0,0 +1,4 @@
[[package]]
name = "notes_from_rust_programing_language_second_edition"
version = "0.1.0"

6
Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "notes_from_rust_programing_language_second_edition"
version = "0.1.0"
authors = ["Zykino <Zykino@users.noreply.github.com>"]
[dependencies]

655
src/main.rs Normal file
View File

@ -0,0 +1,655 @@
fn main() {
println!("Hello, world!");
println!();
chapter_1();
chapter_3();
chapter_2();
chapter_4();
chapter_5();
chapter_6();
}
fn chapter_1() {
println!("Chapter 1: Getting Started");
println!("
The compiler is named `rustc`, the package manager is `cargo`.
Using cargo provide a lot of ease in setting up and building a rust project.
`cargo new <project_name> --bin` create a folder with a hello word program, a git repo ready. *Check the `Cargo.toml` file.*
`cargo run` build (if necessary) and run the program.
`cargo check` check that the build pass but don't create the executable (faster than `cargo build`).
`cargo build --release` create the release executable (with optimizations) and store it under `./target/release`. *Use this build for benchmark.*
`cargo help` to get some help.
`cargo clean` to clean the crate.
");
println!();
}
fn chapter_3() {
println!("Chapter 3: Common Programming Concepts");
variable_and_mutability();
data_types();
println!("Return value of the Functions section: {}", functions(-2, 44));
control_flow();
println!();
}
fn variable_and_mutability() {
println!("Variable and mutability");
println!("We cannot change a variable once it is set. By default they are immutable.");
let x = 5;
println!("The value of x is: {}", x);
// x = 6; // cannot assign twice to immutable variable
println!("We can render the variables mutable by adding the `mut` keyword in the declaration: `let mut toto = 42;`.");
let mut y = 5;
println!("The value of y is: {}", y);
y = 6;
println!("The value of y is: {}", y);
println!("The const should be resolve at compile time. The type must be explicited. They are best for naming hardcoded values.");
const MAX_POINTS: u32 = 100000;
const MAX_POINTS_EASY_READ: u32 = 100_000;
println!("The value of `const MAX_POINTS` is: {}", MAX_POINTS);
println!("The value of `const MAX_POINTS_EASY_READ` is: {} wrote with an underscore: 100_000 = 100000", MAX_POINTS_EASY_READ);
println!("There is a concept of shadowing a variable to reuse the same name but change it's type. We can also keep the variable immutable while still changing it's value but I have yet to find how this can be usefull.");
let z = 1;
println!("The value of z (not mut) is: {}", z);
let z = z + 4;
println!("The value of z (not mut) is: {}", z);
let z = z * 2;
println!("The value of z (not mut) is: {}", z);
let spaces = " "; // We asked to show the number of space char we need to use.
println!("The value of spaces (not mut) is: `{}`", spaces);
let spaces = spaces.len(); // But we prefer to store it as an int;
println!("The value of spaces (not mut) is: `{}`", spaces);
println!();
}
fn data_types() {
println!("Data types");
println!("Data types: Scalar");
println!("`let var_name = value;`");
println!("`let var_name: type = value;`");
println!("Data types: Scalar: Integer (default: i32)");
println!("The differents int types are: [i8, i16, i32, i64, isize], change `i` to `u` for unsigned.");
println!("A visual separator can be used with the `_` char: `10_000`.");
println!("Notation Hex: 0xFF.");
println!("Notation Octal: 0o77.");
println!("Notation Binary: 0b1111_0000.");
println!("Notation Byte (u8): b'A', b' '.");
println!("It is possible to suffix the value with the type (except for the byte form): [42u8, 0x05u16, 0o42i32, 0b1111_0000u64]");
println!("Data types: Scalar: Float (default: f64)");
println!("f32 = float, f64 = double.");
println!("Data types: Scalar: Operations");
println!("[+, -, *, /, %]");
println!("Data types: Scalar: Bool");
println!("[true, false]");
println!("Data types: Scalar: Char");
println!("Limited by the a single quote: `'` (string use double quote `\"`). It represent a Unicode Scalar Value");
let c = 'z';
let z = '';
let heart_eyed_cat = '😻';
println!("Some valid char values: `{}`, `{}`, `{}`", c, z, heart_eyed_cat);
println!("See chapter 8 for strange thing that can happend because of the Unicode representation.");
println!("Data types: Compound");
println!("Data types: Compound: Tuple");
println!("Some kind of not named object");
let tuple: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tuple;
println!("The value of (destructing) x: {}, y: {}, z: {}", x, y, z);
let tup = (500, 6.4, 1);
let x = tup.0;
let y = tup.1;
let z = tup.2;
println!("The value of (tup.0) x: {}, y: {}, z: {}", x, y, z);
println!("Data types: Compound: Array");
println!("An array is fixed on size. For a changing size array see vector in chapter 8.");
let a = [0, 1, 2, 3, 4];
println!("The value of a[0]: {}, a[1]: {}", a[0], a[1]);
println!();
}
fn functions(x: i32, y: i32) -> i32 {
println!("Functions");
println!("The parameters must be typed.");
println!("Parameters values are x: {}, y; {}", x, y);
println!("Statement do not return a value, Expressions does.");
println!("A block is an Expression. (There are also other Expressions.)");
println!("We can transform an Expression into a Statement by closing it with a `;`.");
let a = 5;
let b = {
let a = 3;
a + 1
};
println!("The value of a: {}, b: {}", a, b);
// To escape the curly braces in formated strings we need to format them with others around them: {{}} => {}
println!("Return value type is declared after an arrow symbol: `fn function(param: i16) -> i16 {{}}`");
println!();
// Equivalent to: `return x + y;` == `x + y`
// NOTE: there is no semicolon at the end of the Expression (wich would have transform it into a Statement and produced a compile time error).
x + y
}
fn control_flow() {
println!("Control flow");
println!("If expression");
let number = 6;
if number <= 5 {
println!("Condition was true");
} else {
println!("Condition was false");
}
let divisor = if number % 4 == 0 {
"Four"
} else if number % 3 == 0{
"Three"
} else if number % 2 == 0 {
"Two"
} else {
"Unknown"
};
println!("I found the divisor: {} for the number: {}", divisor, number);
println!("Loops");
let mut number = 6;
loop {
println!("`loop` are infinite unless stoped with a `break` instruction.");
break;
}
while number > 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
let array = [10, 20, 30, 40, 50];
for element in array.iter() {
println!("The value is: {}", element);
}
for element in (1..5).rev() {
println!("{}!", element);
}
println!("TO THE STARS AND BEYOND!!!");
println!();
}
fn chapter_2() {
println!("Chapter 2: Guess game (see project guessing_game)");
println!("To add a dependence add it's `name = version_str` in `Cargo.toml`, see crates.io. To update, run `cargo update`.");
println!("`use std::io;` to include io from std, the use `io::stdin()...`. We can also not include it and directly call `std::io::stdin()...`");
println!("To use a crate we also need to add it in the program with `external crate rand;` followed by `use rand::Rng` if we want.");
println!();
}
fn chapter_4() {
println!("Chapter 4: Ownership");
ownership();
reference_and_borrowning();
slices();
println!();
}
fn ownership() {
println!("Ownership and Functions");
{
let string = String::from("hello"); // string comes into scope
takes_ownership(string); // string's value moves into the function
// println!("{}", string); // ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(5); // x would move into the function, but i32 is Copy (trait)
println!("{}", x); // so it's okay to still use x afterward
} // Here, x goes out of scope, then string. But because string's value was moved,
// nothing special happens.
println!("Return Values and Scope");
{
let s1 = gives_ownership(); // gives_ownership moves its return value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2); // s2 is moved into takes_and_gives_back,
// which also moves its return value into s3
println!("s1: {}", s1);
// println!("s2: {}", s2);
println!("s3: {}", s3);
} // Here, s3 goes out of scope and is dropped.
// s2 goes out of scope but was moved, so nothing happens.
// s1 goes out of scope and is gropped.
println!("Return Values and Scope");
{
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("REMINDER: we can return a tuple and destructure it immediately: `{}`'s lenth is: {} ", s2, len);
}
println!();
}
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called.
// The backing memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope.
// It is just poped out of the stack, nothing special happens.
fn gives_ownership() -> String { // gives_ownership will move its return value
// into the function that calls it
let some_string = String::from("hello");// some_string comes into scope
some_string // some_string is returned and moves out
// to the calling function
}
// takes_and_gives_back will take a String and return one (other)
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into scope
a_string // a_string is returned and moves to the calling function
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String
(s, length)
}
fn reference_and_borrowning() {
println!("Reference and Borrowing");
let mut string = String::from("hello");
let len = calculate_length_borrow(&string);
println!("Now we sended the reference of the string (same as in C++) so we don't need a tuple: \t\t`{}`'s lenth is: {} ", string, len);
let len = calculate_length_borrow_mut(&mut string);
println!("Now we sended the mutable reference of the string (same as in C++) so we don't need a tuple: \t`{}`'s lenth is: {} ", string, len);
println!("Dandling pointers are not possible.");
// dandle();
println!("We cannot returns a reference to a value created in the function but we can return the value (see lifetime in Chapter 10)");
// gives_ownership(); // return the value created in the function instead of the reference.
println!();
}
fn calculate_length_borrow(s: &String) -> usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But nothing happens since it does not have ownership
// of what it refers to.
fn calculate_length_borrow_mut(s: &mut String) -> usize { // s is a reference to a mutable String
s.push_str(", word");
s.len()
} // Here, s goes out of scope. But nothing happens since it does not have ownership
// of what it refers to.
// fn dandle() -> &String { // dandle returns a reference to a String
// let s = String:: from("hello"); // s is a new String
// &s // we return a reference to the String, s
// } // Here, s goes out of scope, and is dropped. Its memory goes away
// // DANGER !
#[allow(unused_mut)]
fn slices() {
println!("Slice");
println!("The range syntax is `..`. A slice is a reference to a range: `&s[0..5]`.");
println!("On `start..end`, `start` is included while `end` is excluded: the range is `[0-5[`.");
println!("If one side of the range correspond to the extremity of collection we can ommit it.`&s[0..s.len()] == &s[..]`");
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..s.len()];
let string = &s[0..s.len()];
let hello_short = &s[..5];
let world_short = &s[6..];
let string_short = &s[..];
assert!((hello == hello_short) == (hello == "hello"));
assert!((world == world_short) == (world == "world"));
assert!((string == string_short) == (string == "hello world"));
println!("WARNING: slices in string do not work if the slice try to cut a char in half.");
println!("Remember the char are UTF-8 and not ASCII so they use 1 or 2 Bytes. (Chapter 8)");
let mut s = String::from("hello world");
let hello = first_word(&s);
//s.clear(); // cannot borrow `s` as mutable because it is also borrowed as immutable
println!("The first word of `{}` is: {}", s, hello);
println!("String literals are slices: `let s: &str = \"hello, world\"`, note that s is a reference str to a string literal.");
println!("We can update our functions to have an API accepting `str` as well as `String`");
let string = String::from("hello world");
let string_literal = "hello world";
// first_word_updated works on slices of `String`s
let word_slice = first_word_updated(&string[..]);
// first_word_updated works on slices of string literals
let word_slice_literal = first_word_updated(&string_literal[..]);
// Because string literals *are* slices already, this works too, witjout the slice syntax!
let word_literal = first_word_updated(string_literal);
// Also found out that `String`s can be dereferenced to `str`
let word = first_word_updated(&string);
println!("String slice: {}", word_slice);
println!("str slice: {}", word_slice_literal);
println!("str: {}", word_literal);
println!("String dereferenced: {}", word);
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("&a[1..3] slice of `{:?}` is: `{:?}`", a, slice);
println!();
}
fn first_word(s: &String) -> &str {
// WARNING: Assumption that s is ASCII. See Chapter 8.
let bytes = s.as_bytes();
for (i, &e) in bytes.iter().enumerate() {
if e == b' ' {
return &s[0..i]
}
}
// Only one word, we return the entire slice
&s[..]
}
fn first_word_updated(s: &str) -> &str {
// WARNING: Assumption that s is ASCII. See Chapter 8.
let bytes = s.as_bytes();
for (i, &e) in bytes.iter().enumerate() {
if e == b' ' {
return &s[0..i]
}
}
// Only one word, we return the entire slice
&s[..]
}
fn chapter_5() {
println!("Chapter 5: Structs");
structs();
struct_examples();
method_syntax();
println!()
}
fn structs() {
println!("Structures syntax and utilisations.");
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("We can access struct element with the dot notation.");
println!("user1.username: {} user1.email: {} user1.active: {} user1.sign_in_count: {}", user1.username, user1.email, user1.active, user1.sign_in_count);
let mut user2 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("To change a `key: value` pair we need to `mut` the whole `struct`: {}", user2.email);
user2.email = String::from("anotheremail@example.com");
println!("To change a `key: value` pair we need to `mut` the whole `struct`: {}", user2.email);
let user3 = build_user(String::from("builderemail@example.com"), String::from("builder's name"));
fn build_user(email: String, username: String) -> User {
User {
email, // email: email,
username, // username: username,
active: true,
sign_in_count: 1,
}
}
println!("We can build a struct from a builder function. There is a short syntax: {}", user3.username);
let user4 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
println!("We can create a struct from an previous one to have it's values as default. {}", user4.active);
struct Color (i32, i32, i32);
struct Point (i32, i32, i32);
let black = Color(0, 0, 0);
let _origin = Point(0, 0, 0);
fn is_black(color: Color) -> bool {
color.0 == 0 && color.1 == 0 && color.2 == 0
}
println!("We can also use Tuple Structs to name a tuple: {}.", is_black(black));
println!("This let us give a meaning to tuples and lock a function to one type.");
// is_black(_origin); // mismatched types
println!("There is also Unit-like structs without any fields which behabe similarly to `()` (See chapter 10)");
println!()
}
fn struct_examples() {
println!("Example: from variables to tuples to structure");
{
let width = 30;
let height = 50;
println!("VARS: The area of the rectangle is {} square pixels.", area(width, height));
fn area(width: u32, height: u32) -> u32 {
width * height
}
}
{
let rect = (30, 50);
println!("TUPLE: The area of the rectangle is {} square pixels.", area(rect));
fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
}
{
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
let rect = Rectangle { width: 30, height: 50 };
println!("STRUCT: The area of the rectangle is {} square pixels.", area(&rect));
println!("rect is {:#?}", rect);
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
}
println!();
}
fn method_syntax() {
println!("Method syntax");
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
{
let rect = Rectangle { width: 30, height: 50 };
println!("STRUCT + Method: The area of the rectangle is {} square pixels.", rect.area());
println!("rect is {:?}", rect);
}
println!("It's possible to have plutiples `impl` blocks. (See chapter 10 to have an usefull way to use it)");
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
{
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
println!("We can add a function inside an `impl` block instead of a method by not using the `self` parameter. Nice use for scoped constructors.");
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
{
let sq = Rectangle::square(3);
println!("We createted a square with an associated function {:?}", sq);
}
println!();
}
fn chapter_6() {
defining_enum();
match_control_flow();
concise_control_flow_if_let();
}
#[allow(dead_code)]
fn defining_enum() {
{
#[derive(Debug)]
enum IpAddrKind {
V4,
V6,
}
let ip_v4 = IpAddrKind::V4;
let ip_v6 = IpAddrKind::V6;
print_ip(ip_v4);
print_ip(ip_v6);
fn print_ip(ip_type: IpAddrKind) {
println!("The ip type is: {:?}", ip_type);
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let _home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let _loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("127.0.0.1"),
};
}
{
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
println!("Instead of combining an enum and a struct, we can put the datas directly into the enum: {:?}", home);
println!("Doing so also permit to have differents types for each elements: {:?}", loopback);
println!("NOTE: The standard library implements an IpAddr enum, beter use it than our custom one 😉.");
impl IpAddr {
fn route(&self) {
// Method would be defined here
unimplemented!()
}
}
println!("Also, like the `struct`, we can implement define mehods and functions to the `enum`");
}
{
println!("The `Option` enum and its advantages over null values");
println!("In rust there is no `null` values like in C/C++/... Instead there is a commonly use enum: the `Option`.");
let _x: i8 = 5;
let _y: Option<i8> = Some(5);
let _z: Option<i8> = None;
// let sum = _x + _y; // no implementation for `i8 + std::option::Option<i8>`
println!("Since _x: {:?} and _y: {:?} are not of the same type, the compiler will not let us compute `int + Option.`", _x, _y);
println!("We will have to take care of the `Option` enum, so checking if the value is _y: {:?} `Some` or _z: {:?} `None`.", _y, _z);
println!("Instead of believing there is a value when really there is a null value.");
}
}
fn match_control_flow() {
unimplemented!()
}
fn concise_control_flow_if_let() {
unimplemented!()
}