@@dario.lencinait would be one thing if it zoomed in on certain key words, but it looks like it just randomly zooms in and out. I agree, it’s distracting and looks shit.
if youre in a situation where you really want to do the trait/interface thing, you could make the function polymorohic like fn speak(x: T) (i believe the notation is something like that) or equivalently fn speak(x: impl Animal) What happens is that rust will create a function for each actual type it's called on, and this happens at compile time, meaning there is no dynamic dispatch shenanigans. This kinda polymorphism is resolved at compile time which makes it a go to for me over boxing dyns which uses dynamic dispatch at runtime. But yeah, in the example in the video, just using an enum works
@@dario.lencina If the enum contains several elements, the match will have to follow, and this will create a strong coupling. So the choice will be between cleaner, more maintainable code, or more powerful, strongly typed code.
but what if you wanted to expose the zoo? like, if someone external user had wanted to declare a new animal(e.g. a duck). this isn't possible with an enum also all the animals will still be stored on the heap, since it's a vector and vectors allocate heap memory to allow for dynamic size. there is still an advantage: all you data is stored in sequence, and not via pointers to some random heap memory, but that's not what you said
I see! Do you save a degree of indirection by storing the objects into the vector instead of a vector of box pointers? Yeah if there’s no way to avoid Box sure then I would use it, but many systems do not need this. Thanks for your feedback!!
@@dario.lencinathe part of the Vector that is on the stack is basically a fat pointer to an array on the heap, so you need go to follow it to find the data, so that's one degree of indirection. when you have a vector of pointers, you have to firstly follow the pointer to the array and then the pointer to the needed data
Yup that is what I am saying, you will have a contiguous block of memory on the heap, so there’s a significant saving by using Vec instead of Vec particularly if you are constantly mutating the vector
@@dario.lencina I recently had a very similar problem where i had Arc, i decided to throw it in the heap and be done so Arc, keep in mind that i can't use an enum because i don't know the structs which will implement the trait.
The struct of arrays pattern is bordering on ECS, which is probably the best choice for a simulation. You can have Animal objects in an array, and extra data for each specific animal can go in a sparce storage container, like BTreeMap or HashMap whose keys are indexes into the Animal array (Vec). The enum in the video makes an assumption that something will never have parts of 2 animal types, and that each variant is about the same size. If you add a large variant, you make each Vec element big enough to hold the largest variant, even though you don't need it. You also have to have the discriminant as part of the Vec element, as well.
The problem of OOP is that there is no standard way of sintax implementation , simula tried to do this but iwhat happened is that we have completely different abstractions in different languages .
Thanks, Dario - this is an example of "just because you can do something, it does not mean that you should do something". I find that too many "advanced" examples in Rust use Box. I have always felt uncomfortable about it. It could just be that I am not experienced enough, but it feels like there are a lot of hidden side effects. I have seen people using Box to return any error, but now the caller has to implement complex code to understand the error. Rust has an elegant way of passing variables using traits, it is called generics. This allows the user to pass and return objects without specifying their exact type ad without using Box.
Good vid, liked the examples. Great example of how to convert traditional OOP habits from other languages to something more rusty! Now turn off the zoom lol.
Thanks for the info! I don't think I have ever been this excited to learn a new language. I currently do web development using PHP and JavaScript. I have dabbled with C, C#, and C++ a little bit, but never got to far with them. So far Rust is holding my attention.
Whoah !! Welcome to the party gman!! Another thing, it is fine to use Box dyn Trait if you don’t have access to all the structs that will be using your lib! This video only applies to code that you control 100%
Your example assumes you know all of the "kind" of animals before hand. In that case yes, there is no need for box . However, trait can be use as a contract where the implementor is unknown. Telling developers to STOP doing X or Y comes across as authoritative. In such a case cover all scenarios covered by your "STOP" doing..
This is fair, indeed you should know all your variants to prevent the Box what is your point? How would you change the title? If you know all the variants then you can avoid the dyn? We have 60 characters and a thumbnail to convey complex ideas 💡 it is pretty tough bud.
You got that right, I am playing with the fact that most people think about inheritance as a core concept in oop vs composition. In fact and to your point the rust book has a whole chapter on oop
@@okie9025 Javascript brain is when your main language is all loosey goosey about types, and garbage collected heavily so you never learn to avoid footguns because the interpreter / jit compiler wipe your butt and change your nappies. When you start programming with a language more close to the metal, you keep these bad habits leading to ass whoopings by the rust compiler for being unruly
@@JavierHarfordlol now that's just childish. I'd argue rust brain is a more terminal condition than "javascript brain", with the mindless cult following behind it and all. Also most people use typescript nowadays, and I don't think you know what a footgun is. Also "ass whoopings by the rust compiler"? LMAO if any compiler is baby oriented it's rustc. Rust isn't hard, it just tries to be too many things at once.
@@pointlessone3702 There's also extra padding bytes to account for the alignment. For instance, if you had an `Option` or an equivalent, this would take up 32 bytes since it's 4 bytes for the tag, 12 bytes of padding, then 16 bytes for the `u128` value, since that `u128` value _has_ to exist at a memory address that is a multiple of 16. There are some cases where both the tag and the padding bytes aren't needed due to the presence of a niche value, such as when you have an `Option` (the inner `u128` value cannot be a 0, so `Option` can use 0 to represent the `None` variant) or when you have an `Option` (references cannot be null, so `Option` can use the null address to represent the `None` variant), but in most cases there will be extra padding bytes.
not the same thing mate. There are millions of animals on earth are you gonna make an enum for all of them? You're missing the point of dynamic dispatch.
i heard that humans, all humans, are born to think in OOP and then some kneecap themselves by going functional or abandoning inheritance for aggregation and i tend to agree. OOP is literally made for humans, it is modeled after intrinsic human reasoning patterns.
Interesting perspective! While OOP can certainly align with how we naturally think about categorizing the world, functional programming offers powerful ways to handle complexity through immutability and pure functions. Both paradigms have their strengths and are tools that can be used depending on the problem at hand. It's all about choosing the right tool for the job!
Sure, OOP does seem natural (a truck is a type of car, which is a type of vehicle, etc.), aggregation makes just as much sense. A truck, a car, and a vehicle all help to transport someone else. By organizing structures based on what values they share, it can become really easy to use code. For example, a bee and a bird can both fly, so it would be easy to just make a Flyable trait to describe the behavior for any flying animal. Inheritance makes it a bit tricky. How would a bird inherit from a bee? We could have a FlyableAnimal class, but bees and birds can also walk. Would they need to inherit from a WalkableAnimal class then? What if we want to include planes? A FlyableObject class? How would that relate to Flyable animals? With a Flyable trait, it would just be right there. Describing objects based on what they can do makes a lot more sense if you are more concerned about the question "what can this do?". I feel like OOP answers the question of "how are these related" which is suitable for some purposes, but a lot of times you will find yourself rewriting code because two things aren't related yet do similar things
@@Redstoner237Channel This is one of the classical failures of the current education system. There is an entire discipline on modeling data. Called, you guessed it, "data modeling." People cry and moan about OOP as if OOP is the issue. In reality it's almost always a poor, or worse, a bad education, on modeling data and data structures. Part of this education centers on both normalization and denormalization. I can't begin to tell you how many times I've met coders who were never taught the most trivial rule of OOP; "is a" vs "has a." If you don't know what this means, you just confirmed you received a second rate education on OOP. Worse still, coders are commonly taught that composition is not only not part of OOP, but an alternative to it. Composition has always been an essential element in data modeling, which means OOP. If you're looking for a point to start, look at data modeling for relational databases. This will eventually lead you to ORM. OOP is rarely the problem. It's almost always an issue with a lacking education or incorrect analysis/decomposition of the problem domain.
According to what I understand , C++ templates works by assuming you, the user of the template, is passing types that has the used methods or field names. In some sense is a sort of Compile-Time reflection. I. E. If the object you're passing has a ".cry()" method for a Car(enterain that a car can cry for a moment) , then you can pass it to the template even though at the time of you writing the template that ".cry()" was for animals/humans. Now for a more finer example, Writers. In a game, a human entity can write. However in low level there's also another kind of Writer, which in Rust are often aliased as Sinks, or just your stdout in many languages. # Key takeaway The point is, there's no guarantees for you, as a library author, to know what the libraries users would pass through the template, and there's also no guarantees that your random joe will know what is actually meant to be passed through the template without type hints like "This receives a Hashing function" or whatever.
@@antoniong4380that’s why in the latest c++ version you can make templates require things You can make it require that it inherits from class X, or make it require that it IS class X, and even make it require that if u do a + b, where a and b are of type T, that they return a specific type. And if those requirements are not met, the compiler will throw an error, and tell you something like “T does not inherit from class Animal” Also, you can store those requirements in “concepts”, so you don’t have to repeat code. Templates are REALLY powerful
@@antoniong4380you can make requirements in c++ templates, and it will throw a detailed compile error now. C++ added concepts in one of those versions. So I can be like T must be a Book, and if I put say pen as my template argument, it will tell me a detailed error “Pen does not meet the requirement: inherits from book” C++ has evolved as well. And I would argue that templates in c++ are more flexible, since u can be like T must either inherit from Book or Food
by the way I really liked that concept (reading nice articles together.) 🙂
Dude this is very insightful feedback!! I will do it much more often!!! 🏃♀️🏃🏃♂️💨
@@dario.lencina yeah, I need more ppl to read articles for my lazy butt
Hahaha!!
@@dario.lencina Yes, waiting
@@MuscleTeamOfficial exactly 🤣
What is going on with your camera zoom?! Super distracting.
Hey! Do you have motion sickness while playing games? I actually really like it 😄
@@dario.lencinait would be one thing if it zoomed in on certain key words, but it looks like it just randomly zooms in and out.
I agree, it’s distracting and looks shit.
@moose6459 hahaha XD got it
@@dario.lencinaits feedback!😄
That is true 😄
if youre in a situation where you really want to do the trait/interface thing, you could make the function polymorohic like
fn speak(x: T)
(i believe the notation is something like that)
or equivalently
fn speak(x: impl Animal)
What happens is that rust will create a function for each actual type it's called on, and this happens at compile time, meaning there is no dynamic dispatch shenanigans. This kinda polymorphism is resolved at compile time which makes it a go to for me over boxing dyns which uses dynamic dispatch at runtime.
But yeah, in the example in the video, just using an enum works
You are right 😄
OOP != Inheritance.
Yup I agree. Rust does have oop via composition with traits 👍
This method is more efficient, but has the disadvantage of requiring a "Speak" overlay in the Animal "enum" with a "match".
Yes! So the code needs a match statement, do you think that is bad?
@@dario.lencina
If the enum contains several elements, the match will have to follow, and this will create a strong coupling. So the choice will be between cleaner, more maintainable code, or more powerful, strongly typed code.
@DevRJPro yup there’s a balance between the two, can’t have your cake and eat it too 👍
@@DevRJPro a proc macro would solve the boilerplate issue ig
@@dario.lencina What if this is crate and i want use to implement his own structure, Hoow will user add it to enum/
but what if you wanted to expose the zoo? like, if someone external user had wanted to declare a new animal(e.g. a duck). this isn't possible with an enum
also all the animals will still be stored on the heap, since it's a vector and vectors allocate heap memory to allow for dynamic size. there is still an advantage: all you data is stored in sequence, and not via pointers to some random heap memory, but that's not what you said
I see! Do you save a degree of indirection by storing the objects into the vector instead of a vector of box pointers?
Yeah if there’s no way to avoid Box sure then I would use it, but many systems do not need this.
Thanks for your feedback!!
@@dario.lencinathe part of the Vector that is on the stack is basically a fat pointer to an array on the heap, so you need go to follow it to find the data, so that's one degree of indirection. when you have a vector of pointers, you have to firstly follow the pointer to the array and then the pointer to the needed data
Yup that is what I am saying, you will have a contiguous block of memory on the heap, so there’s a significant saving by using Vec instead of Vec particularly if you are constantly mutating the vector
Hey!! I found a way to clip the video and remove the heap comment and just focus on the aspect, I appreciate your feedback!!
@@dario.lencina I recently had a very similar problem where i had Arc, i decided to throw it in the heap and be done so Arc, keep in mind that i can't use an enum because i don't know the structs which will implement the trait.
Another way: Just create two vectors with one type of animal each -> no matching, no boxing, no enums, no traits
😱😱😱nice!!
The struct of arrays pattern is bordering on ECS, which is probably the best choice for a simulation. You can have Animal objects in an array, and extra data for each specific animal can go in a sparce storage container, like BTreeMap or HashMap whose keys are indexes into the Animal array (Vec). The enum in the video makes an assumption that something will never have parts of 2 animal types, and that each variant is about the same size. If you add a large variant, you make each Vec element big enough to hold the largest variant, even though you don't need it. You also have to have the discriminant as part of the Vec element, as well.
The problem of OOP is that there is no standard way of sintax implementation , simula tried to do this but iwhat happened is that we have completely different abstractions in different languages .
Also please post the article URL that you were reading please
here you go my friend: courses.grainger.illinois.edu/cs225/fa2022/resources/stack-heap/
Thanks, Dario - this is an example of "just because you can do something, it does not mean that you should do something".
I find that too many "advanced" examples in Rust use Box. I have always felt uncomfortable about it. It could just be that I am not experienced enough, but it feels like there are a lot of hidden side effects.
I have seen people using Box to return any error, but now the caller has to implement complex code to understand the error.
Rust has an elegant way of passing variables using traits, it is called generics. This allows the user to pass and return objects without specifying their exact type ad without using Box.
Exactly! So many times you can actually avoid it. With that said, sometimes you cannot avoid the box and that is fine too
is this good practice?
fn sample(a: u8, b: u8) -> Result {}
Why do you need the Box for the error? Is there a way for you to know what the error is going to be at compile time?
Good vid, liked the examples. Great example of how to convert traditional OOP habits from other languages to something more rusty! Now turn off the zoom lol.
Thanks for the info! I don't think I have ever been this excited to learn a new language. I currently do web development using PHP and JavaScript. I have dabbled with C, C#, and C++ a little bit, but never got to far with them. So far Rust is holding my attention.
Whoah !! Welcome to the party gman!! Another thing, it is fine to use Box dyn Trait if you don’t have access to all the structs that will be using your lib! This video only applies to code that you control 100%
Please make more videos like this one
me gustaria que rust tuviera un entorno de programación orientada a objetos
Hey Joel! De hecho si lo tiene! Checa este capítulo doc.rust-lang.org/book/ch17-00-oop.html
@@dario.lencina Muchas gracias, deseo crear librerias en Rust para luego usarlas en Python, estoy aprendiendo Rust. Gracias por el contenido
Si! Es una gran manera de mejorar el performance de tu aplicación en Python. Ya viste mi video en maturin?
Is it a bad practice if first variant is large and second variant is small in memory?
@@kuqmua755 no need to worry at all! The impact in performance is negligible
Your example assumes you know all of the "kind" of animals before hand. In that case yes, there is no need for box . However, trait can be use as a contract where the implementor is unknown. Telling developers to STOP doing X or Y comes across as authoritative. In such a case cover all scenarios covered by your "STOP" doing..
This is fair, indeed you should know all your variants to prevent the Box what is your point?
How would you change the title? If you know all the variants then you can avoid the dyn? We have 60 characters and a thumbnail to convey complex ideas 💡 it is pretty tough bud.
@@dario.lencina Something on the line of "Another way to avoid using Box" or "Alternative to Box"
Ok I will think harder about how to name these things moving forward, thanks for your feedback ✅👍
you're still using objects...structures are still objects
You got that right, I am playing with the fact that most people think about inheritance as a core concept in oop vs composition. In fact and to your point the rust book has a whole chapter on oop
insightful! thank you!
thanks Enes! I am going to go much harder on Rust moving forward my friend!!
yes! just KISS of code
Aw yeah 🤘🤘🤘
Keeping watch for your next videos, this was a good one thanks! More Anti-Javascript brain videos please!
Thanks Javier!! It’s been challenging for me to get rid of some js bad habits and it makes me super happy that this helped you too!!
wtf is javascript brain?
@@okie9025 Javascript brain is when your main language is all loosey goosey about types, and garbage collected heavily so you never learn to avoid footguns because the interpreter / jit compiler wipe your butt and change your nappies. When you start programming with a language more close to the metal, you keep these bad habits leading to ass whoopings by the rust compiler for being unruly
@@JavierHarfordlol now that's just childish. I'd argue rust brain is a more terminal condition than "javascript brain", with the mindless cult following behind it and all. Also most people use typescript nowadays, and I don't think you know what a footgun is. Also "ass whoopings by the rust compiler"? LMAO if any compiler is baby oriented it's rustc. Rust isn't hard, it just tries to be too many things at once.
@@okie9025 is when you want to force JavaScript design pattern into other languages
Won't it take more memory, because of the enum elements size?
The size of enum is the size of the biggest variant + size of usize for the tag (if there's more than one variant in the enum).
@@pointlessone3702 There's also extra padding bytes to account for the alignment. For instance, if you had an `Option` or an equivalent, this would take up 32 bytes since it's 4 bytes for the tag, 12 bytes of padding, then 16 bytes for the `u128` value, since that `u128` value _has_ to exist at a memory address that is a multiple of 16. There are some cases where both the tag and the padding bytes aren't needed due to the presence of a niche value, such as when you have an `Option` (the inner `u128` value cannot be a 0, so `Option` can use 0 to represent the `None` variant) or when you have an `Option` (references cannot be null, so `Option` can use the null address to represent the `None` variant), but in most cases there will be extra padding bytes.
and Dog(Dog)Cat(Cat) ain't ugly? mmmmmhm.
Well, in retrospect… yes!!
@@dario.lencina there ought to be a guide to non-ugly rust ;)
not the same thing mate. There are millions of animals on earth are you gonna make an enum for all of them?
You're missing the point of dynamic dispatch.
There are many valid cases for enums and also for dynamic dispatch turns out that there are valid use cases for both
i heard that humans, all humans, are born to think in OOP and then some kneecap themselves by going functional or abandoning inheritance for aggregation and i tend to agree. OOP is literally made for humans, it is modeled after intrinsic human reasoning patterns.
Interesting perspective! While OOP can certainly align with how we naturally think about categorizing the world, functional programming offers powerful ways to handle complexity through immutability and pure functions.
Both paradigms have their strengths and are tools that can be used depending on the problem at hand. It's all about choosing the right tool for the job!
Sure, OOP does seem natural (a truck is a type of car, which is a type of vehicle, etc.), aggregation makes just as much sense. A truck, a car, and a vehicle all help to transport someone else. By organizing structures based on what values they share, it can become really easy to use code.
For example, a bee and a bird can both fly, so it would be easy to just make a Flyable trait to describe the behavior for any flying animal. Inheritance makes it a bit tricky. How would a bird inherit from a bee? We could have a FlyableAnimal class, but bees and birds can also walk. Would they need to inherit from a WalkableAnimal class then? What if we want to include planes? A FlyableObject class? How would that relate to Flyable animals? With a Flyable trait, it would just be right there.
Describing objects based on what they can do makes a lot more sense if you are more concerned about the question "what can this do?". I feel like OOP answers the question of "how are these related" which is suitable for some purposes, but a lot of times you will find yourself rewriting code because two things aren't related yet do similar things
@Redstoner237Channel this explanation is sick!
@@Redstoner237Channel This is one of the classical failures of the current education system. There is an entire discipline on modeling data. Called, you guessed it, "data modeling." People cry and moan about OOP as if OOP is the issue. In reality it's almost always a poor, or worse, a bad education, on modeling data and data structures.
Part of this education centers on both normalization and denormalization. I can't begin to tell you how many times I've met coders who were never taught the most trivial rule of OOP; "is a" vs "has a." If you don't know what this means, you just confirmed you received a second rate education on OOP. Worse still, coders are commonly taught that composition is not only not part of OOP, but an alternative to it. Composition has always been an essential element in data modeling, which means OOP.
If you're looking for a point to start, look at data modeling for relational databases. This will eventually lead you to ORM.
OOP is rarely the problem. It's almost always an issue with a lacking education or incorrect analysis/decomposition of the problem domain.
And the problem is even worse if you do this in an async context.
Box go brrrt
Hahaha those genetics can get pretty unhinged 😆😆😆😆
even c++ templates are easier to understand than rust
skill issue
@theairaccumulator7144 @aykxt said it ^^ you need more hours in the saddle
According to what I understand , C++ templates works by assuming you, the user of the template, is passing types that has the used methods or field names. In some sense is a sort of Compile-Time reflection. I. E.
If the object you're passing has a ".cry()" method for a Car(enterain that a car can cry for a moment) , then you can pass it to the template even though at the time of you writing the template that ".cry()" was for animals/humans.
Now for a more finer example, Writers.
In a game, a human entity can write. However in low level there's also another kind of Writer, which in Rust are often aliased as Sinks, or just your stdout in many languages.
# Key takeaway
The point is, there's no guarantees for you, as a library author, to know what the libraries users would pass through the template, and there's also no guarantees that your random joe will know what is actually meant to be passed through the template without type hints like "This receives a Hashing function" or whatever.
@@antoniong4380that’s why in the latest c++ version you can make templates require things
You can make it require that it inherits from class X, or make it require that it IS class X, and even make it require that if u do a + b, where a and b are of type T, that they return a specific type. And if those requirements are not met, the compiler will throw an error, and tell you something like “T does not inherit from class Animal”
Also, you can store those requirements in “concepts”, so you don’t have to repeat code. Templates are REALLY powerful
@@antoniong4380you can make requirements in c++ templates, and it will throw a detailed compile error now. C++ added concepts in one of those versions. So I can be like T must be a Book, and if I put say pen as my template argument, it will tell me a detailed error “Pen does not meet the requirement: inherits from book”
C++ has evolved as well. And I would argue that templates in c++ are more flexible, since u can be like
T must either inherit from Book or Food
grazie
Di niente amico!