learning-rust/src/jour1.rs
2023-11-15 10:00:58 +01:00

619 lines
13 KiB
Rust

/*
# Les commentaires
Les commentaires qui commencent par `///` ou `//!` sont réservés pour la doc.
*/
// Les types primitifs
// Types simples
fn integers() {
let _x = 42i8;
let _x = 42i16;
let _x = 42i32;
let _x = 42u8;
let _x = 42u16;
let _x = 42u32;
let _x = 42isize; // Architecture dependent
let _x = 0x2a;
let _x = 0o52;
let _x = 100_000;
}
fn floats() {
let _x = 12.5; // By default it's f64
}
fn bools() {
let a_boolean: bool = true;
assert!(a_boolean == true);
assert!(false != true);
}
fn chars() {
let unicode_codepoint = '√';
assert!(unicode_codepoint == '√');
}
// Container types
fn tuples() {
let tuple = (1.0, 2.0, 10i64);
let partially_inferred: (_, i32, i32) = (10, 10, 12);
let tuple2 = (tuple.0 * 10.0, tuple.1 * 10.0);
assert!(tuple.0 == 1.0);
assert!(tuple.2 == 10);
assert!(partially_inferred.0 == 10);
assert!(tuple2.0 == 10.0);
}
fn arrays() {
// Can't mix types in arrays.
let inferred = [42, 42, 42]; // Inferres i32, i32, i32
let tab: [u64; 4] = [42, 42, 42, 42];
assert!(tab[0] == 42);
assert!(inferred[0] == 42);
}
fn a_function(x: i64) -> i64 {
// On function parameters, types are mandatory.
assert!(x == x); // It has a semicolon, it's an instruction.
x * 2 // Implicit return, no semicolon (an expression)
// The return keyword is unused.
// Rust does not like early returns ☹
}
fn variables() {
// Each variable has a type.
// But the compiler is great at inferring, fully or partially.
let a = 42; // Typically here we don't tell the type.
a_function(a); // Forces a to be inferred as i64 as a_function excepts an i64.
}
fn mutable_variables() {
// Variables are immuable by default.
let mut a = 42; // Can be rebound.
assert!(a == 42);
a = 43; // Rebinding
assert!(a == 43);
}
// Used by const PI, executed AT COMPILE TIME
const fn compute_pi() -> f64 {
3.14159265358979323846
}
const PI: f64 = compute_pi(); // Computed at compile time!!
fn annotations() {
#[allow(non_snake_case)]
// Compiler won't whine about the bad casing.
let Foo = 42;
assert!(Foo == 42);
}
// rustfmt : Formatte un fichier
// cargo fmt : Formatte un projet
fn masquage() {
let a = 42;
{
let a = 43;
assert!(a == 43);
}
assert!(a == 42);
}
fn mutable_tuple() {
// Tuples can be mutable.
let mut mutuple = (1, 2, 3);
mutuple.1 = 0;
assert!(mutuple.1 == 0);
}
fn blocs_has_value() {
let pi = {
let a = 3.0;
let b = 0.141592;
a + b
};
assert!(pi > 3.0);
}
fn the_if_instruction() {
let number = 3;
if 2 < number && number < 5 {
assert!(true);
} else {
assert!(false);
}
}
fn the_if_expression() {
let condition = true;
let number = if condition { 5 } else { 6 };
assert!(number == 5);
}
fn in_fact_if_is_always_an_expression() -> i64 {
let condition = true;
if condition {
0x42i64
} else {
0x21i64
}
}
// fn the_loop_loop() {
// loop {
// println!("Will print at nauseum");
// }
// }
fn breaking_the_loop_loop() {
let mut count = 10;
loop {
count -= 1;
if count == 0 {
break;
}
}
// Loops can be named using:
// 'name loop { ...
}
fn break_can_take_value() -> i32 {
let mut count = 10;
let result = loop {
count += 1;
if count == 20 {
break count * count;
}
};
result
}
fn loop_is_an_expression() -> i64 {
let mut count: i64 = 10;
loop {
count -= 1;
if count < 5 {
break count;
}
}
}
fn while_loop() {
let mut count = 10;
while count > 0 {
count -= 1;
}
}
fn for_loop() {
for i in 0..3 {
// 0, 1, 2
assert!(i < 10);
}
for j in 0..=3 {
// 0, 1, 2, 3
assert!(j < 10);
}
let array = [1, 2, 3];
for k in array {
assert!(k > 0);
}
}
fn exercise_1_to_the_power_of_funky(mut n: i64, mut p: i64) -> i64 {
let value = n;
loop {
if p <= 1 {
break n;
}
n *= value;
p -= 1;
}
}
fn power(n: i64, p: i64) -> i64 {
let mut value = n;
for _ in 1..p {
value *= n;
}
value
}
fn as_keyword() -> i64 {
let n = 42i8;
99i64 * (n as i64)
}
use std::time::Duration;
fn struct_types() {
let five_seconds = Duration::new(5, 0); // new is just a normal function.
assert!(five_seconds.as_secs_f64() == 5.0); // This is some kind of maybe a method.
}
fn enumerated_types() {
// "some types" "enums"
// The most common is "Option".
// Option is usefull because `null` does not exists in rust.
//
// pub enum Option<T> {
// None,
// Some(T),
// }
let n = Some(2);
assert!(!n.is_none());
assert!(n.is_some());
assert!(n.unwrap() == 2);
match n {
Some(k) => {
assert!(k == 2);
}
None => {
assert!(false);
}
}
}
// Enum are enumerated named values, not enumerated types. Types are lost at
// runtime (type erasure).
enum Test<T> {
A(T),
B(T),
C(T),
}
fn play_with_enum(n: Test<u64>) {
match n {
Test::A(_a) => {
// do something...
}
Test::B(_b) => {
// do something...
}
Test::C(_c) => {
// do something...
}
}
}
fn f_strings() {
let n = 42;
println!("La réponse à la question est {n}"); // like Python f-strings.
println!("La réposne à la question est {}", n);
let m = dbg!(n) + dbg!(n); // dbg! returns given value (not println!).
assert!(m == 84);
}
// Macros : functions ending with bangs.
fn display_difference(dur: Option<Duration>) {
match dur {
None => {
println!("No duration.");
}
Some(n) => {
println!("Duration: {n:?}")
}
}
}
fn durées() {
let d1 = Duration::from_secs(60);
let d2 = Duration::from_secs(3600);
assert!(d2.saturating_sub(d1) == Duration::from_secs(3540));
assert!(d1.saturating_sub(d2) == Duration::from_secs(0));
display_difference(d1.checked_sub(d2));
display_difference(d2.checked_sub(d1));
}
fn ownership() {
// En dehors des références et des pointeurs :
// chaque valeur connaît son propriétaire (le "propriétaire" c'est une variable).
let mut s = String::from("hello"); // Sur la heap car taille variable.
s.push_str(", world!");
assert!(s == "hello, world!");
}
// fn moving_ownership() {
// let s1 = String::from("hello");
// let s2 = s1; // Ownership transfer
// dbg!(s1); // Error, s1 is no longer owner of the hello string.
// // The same happen when we call a function: giving an argument is giving ownership.
// There's a clone() method to deep copy data, so we can have two ownerships.
// If it's a number instead of a string, there's no issue, because it's a primitive type allocated on the stack.
// if it's on the stack : copy is implicit (they implement the Copy trait).
// if it's on the heap : copy has to be explicit.
// }
fn len(s: &String) -> usize {
s.len()
}
fn change(s: &mut String) {
// This is a mutable reference
// There can be only one mutable reference to a value.
s.push_str(".")
}
fn borrowing() {
// It's like pointers but it is called references.
let mut s = String::from("Hello");
let len = len(&s);
assert!(len == 5);
change(&mut s);
assert!(s == "Hello.");
}
fn slices() {
let s = String::from("Hello world");
let hello = &s[0..5]; // A reference to a slice of the string.
let world = &s[6..11]; // Another reference to another slice of the string.
assert!(hello == "Hello");
assert!(world == "world");
}
fn their_concat(mut s1: String, s2: String) -> String {
// It's not a copy, it's ownership change.
s1.push_str(&s2);
s1
}
fn concat(s1: &String, s2: &String) -> String {
let mut s3 = String::from("");
s3.push_str(s1);
s3.push_str(s2);
s3
}
// &str is a string slice, or a static string in the binary (are they really the same?)
// String is a string object, in the heap.
fn concat2(s1: &mut String, s2: &String) {
s1.push_str(s2);
}
fn concat3(s1: &str, s2: &str) -> String {
// Those are string slices
format!("{}{}", s1, s2)
}
fn repeat_str(s1: &str, times: usize) -> String {
let mut result = String::from("");
for _ in 0..times {
result.push_str(s1);
}
result
}
fn exercise3() {
let hello = String::from("Hello");
let world = String::from(" world!");
let hello_world = concat(&hello, &world);
assert!(hello_world == "Hello world!");
let mut hello2 = String::from("Hello");
let world2 = String::from(" world!");
concat2(&mut hello2, &world2);
assert!(hello2 == "Hello world!");
let hello3 = String::from("Hello");
let world3 = String::from(" world!");
assert!(concat3(&hello3, &world3) == "Hello world!");
let space = " ";
let spaces = repeat_str(space, 4);
assert!(spaces == " ");
let their_hello = String::from("Hello");
let their_world = String::from(" world!");
// Funnily enough, their_hello is not mut, but it will be mutated by their_concat.
assert!(their_concat(their_hello, their_world) == "Hello world!");
// Which is NOT bad in any way because their_hello is GIVEN to their_concat
// we can no longer use it, so we don't really care that it's mutated.
}
// Creating types
type Point1D = (f64, String);
struct Point2D {
name: String,
x: f64,
y: f64,
}
fn format_point(p: Point1D) -> String {
format!("Point({}, {:?})", p.0, p.1)
}
fn add_points1d(p1: &Point1D, p2: &Point1D) -> Point1D {
(p1.0 + p2.0, format!("{} + {}", p1.1, p2.1))
}
struct Point4D(f64, f64, f64, f64); // Behave like a tuple.
// A struct is allowed to have no fields, may be usefull to "store" methods in it.
fn test_points() {
let p1: Point1D = (0.0, "Origin".to_owned());
let p2: Point1D = (10.0, "Somewhere".to_owned());
let p3 = add_points1d(&p1, &p2);
assert!(p3.0 == 10.0);
assert!(format_point(p3) == "Point(10, \"Origin + Somewhere\")");
//dbg!(add_gpoints(&m1, &m2));
let p = Point2D {
name: String::from("origin"), // If I have a variable named
// `name`, just `name` would be enough instead of `name: name`.
x: 0.0,
y: 0.0,
// Also using `..p1` would get all values from p1, kind of **kwargs.
};
println!("{}: {}, {}", p.name, p.x, p.y);
let p4: Point4D = Point4D(0.0, 0.0, 0.0, 0.0);
assert!(p4.0 == 0.0);
}
// Enums
enum Color {
Red,
Green,
Blue,
}
enum ProcessStatus {
InProgress,
Error(String),
Success { output: String, duration: Duration },
}
fn test_enum() {
let _color = Color::Red;
let _color = Color::Green;
let color = Color::Blue;
match color {
Color::Blue => {
println!("Is blue");
}
_ => {
println!("Is not blue");
}
}
let _p1 = ProcessStatus::InProgress;
let _p2 = ProcessStatus::Error(String::from("Timeout"));
let p3 = ProcessStatus::Success {
duration: Duration::from_secs(25),
output: String::from("Success"),
};
match p3 {
// Match can has guards exactly like in Python
ProcessStatus::InProgress => {
println!("In progress...");
}
ProcessStatus::Error(e) => {
println!("Errorred {}", e);
}
ProcessStatus::Success {
duration: d,
output: o,
} => {
println!("Done in {:?} seconds: {}", d, o);
}
}
}
// The if let
fn test_if_let() {
let p = ProcessStatus::Error(String::from("Timeout"));
match p {
ProcessStatus::Error(ref e) => println!("Process failed: {e}"),
_ => (),
}
// same as
if let ProcessStatus::Error(ref e) = p {
println!("Process failed: {e}");
}
}
// No return annotation, inferred as an empty tuple, some kind of "void".
pub fn main() {
println!("Hello World");
bools();
chars();
integers();
floats();
tuples();
arrays();
a_function(0x42i64);
variables();
mutable_variables();
assert!(PI > 3.0);
annotations();
masquage();
blocs_has_value();
mutable_variables();
mutable_tuple();
the_if_instruction();
the_if_expression();
assert!(in_fact_if_is_always_an_expression() == 0x42);
breaking_the_loop_loop();
break_can_take_value();
assert!(loop_is_an_expression() == 4);
while_loop();
for_loop();
let n = 2;
let r = 10;
assert!(exercise_1_to_the_power_of_funky(n, r) == 1024);
assert!(power(power(3, 3), 3) == 19683);
assert!(power(power(power(3, 3), 3), 3) == power(3, power(3, 3)));
// dbg!(power(3, power(3, power(3, 3)))); // overflow
as_keyword();
struct_types();
enumerated_types();
let test: Test<u64> = Test::A(1);
play_with_enum(test);
let test: Test<u64> = Test::B(1);
play_with_enum(test);
let test: Test<u64> = Test::C(1);
play_with_enum(test);
f_strings();
durées();
ownership();
borrowing();
slices();
exercise3();
test_points();
test_enum();
test_if_let();
}