Structs 🥸
Like C structs these types are a set of different fields with a type and it can be used for almost everything. A cool feature that Rust have, are methods
for structs which is different from a function
.
"Normal" structs
Also a cool thing about Rust, that is like Javascript (ECMA6), the spread operator, which let us take the values from a struct and place them into another one in the same fields. Another feature like in ECMA6 is that when you have variables with the same name as a property from a struct, you can use the name instead of variable and value.
// creating an Animal struct
struct Animal {
name: String,
age: u8,
death: bool,
type: String,
};
let name = "Josefina";
// instanciate a new Animal
let bonnie = Animal {
name: String::from("Bonnie"),
age: 5,
death: false,
type: String::from("dog"),
};
// normal re-use from a struct to a new one
let josefina = {
name, // instead of name: name (which is redundant)
// also don't do these, this way, do it with the spread operator
age: bonnie.age,
death: bonnie.age,
type: bonnie.type,
};
// re-use properties from a struct rust way
let guru = Animal {
name: String::from("Guru"),
age: 9,
..bonnie,
};
Tuple struct
You can use tuples as a struct instead of a normal struct. So instead of having a struct Coordinates
fields with names x
, y
. We can have a struct tuple, which has x
and y
without the names.
struct Coordinates_like_MEH { X: i32, Y: i32 };
// do this
struct Coordinates (i32, i32);
let origin = Coordinates(0, 0);
println!("x: {}, y: {}", origin.0, origin.1);
This is useful because sometimes the name of the attributes annoy more than helps.
Unit-Like structs
This are structs that does not have fields. I mean this is cool, because if you have something you don't know what it gives you, you can use this structs. (We will see on later posts, because is chapter 10)
Also, remember: Be careful with the context!!!! Because of ownership OwO
An important thing about structs, and in general, is that when you use println!()
and types like integer, float, etc. they have implementd a Display
method.
Structs does not have that, so you need to implement it or use Debug
that can be used to print data of a field or the hole struct.
#[derive(Debug)] // u need this otherwise you won't be able to use debug
struct Meeeep {
wtf_level: i32,
};
let meep007 = Meeeep {
wtf_level: 10000,
};
println!("meep007: {:?}", meep007);
// output: meep007: Meeeep { wtf_level: 10000, }
println!("meep007: {:#?}", meep007);
// output:
// meep007: Meeeep {
// wtf_level: 10000,
// }
dbg!(&meep007);
// output:
// [src/main.rs:20] &meep007: Meeeep {
// wtf_level: 10000,
// }
let _miau = Meeeep {
wtf_level: dbg!(-10 * meep007.wtf_level / 9086),
};
// output:
// [src/main.rs:25] -10 * meep007.wtf_level / 9086 = -11
Methods
The difference between a function and a method is that, a method is part of a struct, so it is defined on the context of that struct.
struct Animal {
name: String,
age: u8,
death: bool,
type: String,
};
// how to create a method
impl Animal {
fn say_guau (&self) {
println!("{} says FOLOOOOWWW MEEEEEE!!!", self.name);
}
}
let name = "Josefina";
let bonnie = Animal {
name: String::from("Bonnie"),
age: 5,
death: false,
type: String::from("dog"),
};
say_guau(bonnie);
IMPORTANT!!!
&self
means a variable called self
of type Self
. And what is self?? Well... I think it describes its self uwu, is a reference to an instance of type (in this case) Animal.
If you read the docs, you will see that it talks about ->
which I am not going to cover. Sorry UwU
To end with structs, you can use multiple impl
if you want, but I don't see why you would use two impl
for the same struct when you can have everything in one.
Enums 🤪
At this point, I am tired, I want to sleep and have dinner :v butttt I am going to finish the post. So:
Enums are as you can think of, enumerators, this means that you have a set of values that can be matched. Also enums can have methods like structs.
#[derive(Debug)]
enum AnimalType {
DOG,
CAT,
LION,
IDK,
}
impl AnimalType {
fn wtf_is_this_shit(&self) {
println!("Tricky tricky lemons q-easy!!! {:?}", AnimalType::IDK);
}
}
fn main() {
let owo = AnimalType::DOG;
owo.wtf_is_this_shit();
}
You can specify the type of a field:
enum AnimalType {
DOG,
CAT,
LION,
IDK(i32),
}
In the book there is an explanation about null values which I recommend reading.
But what it says it that in Rust you cannot have null values never!! There is an enum Option<T>
being T a variable type which is used to handle not havind null values. This enum has None
, and Some(T)
. Also T is used for generalization, we will see it on another post.
Take in consideration that you cannos operate with a variable which is Option<T>
and a T
variable type. There are ways to convert Option<T>
on T
. docs
let some_number = Some(5);
let num = 9;
let sum = num + some_number; // Will fail because Option<i32> != i32
// using None
// we are saying that it will be an Option which type is String but we don't know its value right now.
let some_value: Option<String> = None;
Using match with enums. match
is a useful and important keyword for enum, becuase is the way of having multiple actions depending on which one is true.
fn main() {
enum Drink {
WATER,
BEER,
COKE,
WINE,
}
match Drink::WATER {
Drink::WATER => println!("Nice :D"),
Drink::BEER => println!("IDIOT^4"),
Drink::COKE => println!("IDIOT^10"),
Drink::WINE => println!("IDIOT^0"),
}
}
The difference between match and if, is that "if" needs to have a boolean condition.
When match is executes, it matches the value of the code associated with the pattern given, if the pattern does not match the value, it continues to the next arm, otherwise it executes what is on tje right side of "=>" (the arm).
Match patterns are evaluated in order!!!! So be caredul when using the "catch-all" pattern.
If you need to execute multiple statements, use {}
(curly braces).
The code related with each arm (option) is an expression, and if you remember, that means that it "returns" that value.
Using match with Option<T>
.
// docs example
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five); // we will get 6 because Some(i) == Some(5)
let none = plus_one(None); // We will get None, because None == None
}
In case you need a pattern like an "else", because you don't need to check for 1000 cases that are the same code, you use the catch-all patterns. You can use or not the value of the "catch-all" pattern, lets see:
let dice_roll = 9;
// handling catch-all with a variable to use it, becuase be want
match dice_roll {
3 => println!("haha"),
7 => println!("hehe"),
other => println!("This is s* {}", other),
}
// not handling cath-all
match dice_roll {
3 => println!("haha"),
7 => println!("hehe"),
_ => (),
}
Lastly there is something called if let
that is used when we want to match only one pattern and we don't care about them.
// docs example
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
// equals
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
}
I think you can use if let
when you have a huge enum with a lot of things and you just need to find one pattern and with the other don't need to do anything.
So this is all... for now I wish you a good day!!
Top comments (0)