WHAT I DIDNT EXPECT THIS TO BLOW UP SO FAST. But as many have pointed out, no matter how confident I sound, I’m still a noob at c++ and rust. So pls take everything shown with a massive grain of salt. And pls go easy on the criticism 😢
Much of this reminds me of my experience learning rust. It was painful at times battling the borrow checker. But it has "unintentionally" taught me valuable lessons sometimes, how sometimes multiple mutable references can shoot you in the foot, or forcing me to take a deeper thought to what data types I'm working with. Sometimes though because of the borrow rules it's easy to take short cuts like cloning stuff willy nilly or unwrapping everything. But over time your intuition will develop to know immediately what will work well in a rust way. Welcome to the tribe :P Some things I love about the language: Enums (Every time I use another language I miss the way Rust Enums work), enforced naming convention(clean api's and for collaboration), excellent compiler error messages, Clippy (tool for giving you tips on improvements on your code)
I dont understand enums at all. How are they different from something like a tuple? Im chronically python brained i cant wrap my head around what a result enum is
@@Gigachad-mc5qz An enum *definition* is a set of valid types (or variants) a value CAN take on, but only takes one one of them. A simple example would be a boolean (in rust syntax): enum bool { False, // 0 True, // 1 } instantiated as: let a = bool::False; Under the hood, these variants are just stored as integers ranging from 0 to the number of variants-1. so `False as u32` would return 0. Results are exactly the same way, except the `Some` variant holds a value So a good way of thinking about them is booleans with more options (and extra steps depending on the language) Hope this helps :)
something to note on your explanation at 3:32 - vectors and other containers are not automatically referenced when passed in cpp. this might also be a source of issues in your chess code. function parameters are ALWAYS passed by value (calling the copy constructor) and only referenced when you declare the type as a reference (const std::vector& myints). secondly i dont know what syntax you used for the cpp hashmap and linkedlist example at around the same time stamp - but those dont exist. not under those names anyway. a std::unordered_map is a hashmap-like container which also doesn't have the initialization syntax shown there. good work though i understand the beginners trouble with cpp - nice end montage lol
Ohhh I see. Yeah for the hashmap and linked list I was too lazy to find the actual syntax so I just put some BS there. There was supposed to be a comment at the start that said I was too lazy but I guess it got dropped out 🤷. Thx for the advice though!
That's what I was thinking, too. ChatGPT is great for specific, hard-to-Google questions, but you should use it only as a way to orient yourself in the right direction, not try to have it do the work for you.
9:34 you don't actually need mutable references, as you don't change elements but only read them, so shared references will do. However you can even get two mutable references to elements in one vector using split_at_mut method
4:40 i32 is a 32 bit integer that can be negative. u32 is a 32 bit integer that can't be negative, so unsigned integers are appropriate types for indexing. And usize is basically just a 32 or 64 bit unsigned integer, depending on whether or not your PC is 32 or 64 bit.
PS good video. It reminds me when I was younger and in a very similar stage, except Rust wasn't a thing at the time 😅. I went from Python - > C++ - > Rust as well. Something I will say is Rust basically forces you to learn concepts that C++ doesn't, but you can guarantee you'll shoot yourself in the foot if you don't understand them in C++. And good luck on your career 🙂
@@darkfllame I doubt we'll ever use 128-bit addresses, the most we can put in a machine today is only 2^42. Some software actually narrows to 32 bits by storing indexes into a vector/arena and caching the high bits elsewhere - even if we needed more than 2^64 bytes addressable in some compute cluster, we would partition it into separate address spaces rather than widen everything. However, usize is not necessarily enough to store a pointer. Rust pointers may contain a vtable/slice length, or even CHERI provenance information which is lost when casting to integer. It's enough to store pointer _offsets_ though, like in slice indexing.
A "usize" is an unsigned integer with the size of the platform. Example: "usize" on a 64-bit CPU is an unsigned 64-bit integer. There is also "isize" which is the same but signed. Other non-dynamic number types in Rust are: u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 f32 f64 They are pretty self-explanatory, examples: u8 - Unsigned 8-bit integer. (AKA a byte.) f64 - 64-bit floating point number. i32 - Signed 32-bit integer. (Signed just means that it can also store negative numbers by giving up some of the larger unsigned numbers that it could store if it was unsigned.)
A `String` is a Boxed string allocated on the heap. This isn't a reference, we own these. A &str` is a string slice; a string slice is a reference (non-null pointer) to an array of UTF-8 characters. We don't own the data referred to by the reference. Rust has a string slice separate from a normal slice (`&[T]`) so we know that the data within is valid UTF-8. I started learning Rust after C. For C, the first thing I did was read "The C Programming Language" by K&R. For Rust, I read "The Rust Programming Language." I don't remember programming in Rust being any more difficult to me than C. Maybe I've just got amnesia. I have never had any success with LLMs. When I started learning programming (not that long ago) they didn't exist. Whenever I've tried to use them, they've only ever misled me and wasted my time. I know how to ctrl-f, however LLMs don't. FYI: Rust is the programming language I'w most comfortable in.
To clarify, `String` is not the same as `Box`, while both are owned heap allocated `str`, the difference is that you can modify `String`'s length (you cannot do this with `Box`, though you can modify its content)
You probably don't remember it being hard because Rust isn't that hard coming from C. Arguably, it's even easier-a lot of the things you have to manually remember how to do in C (gotta love remembering to pass the length of an array alongside the actual array) are easier because of Rust's higher-level type system
The borrow checker is actually quite simple. If you put something into a function call by value, it deallocates the variable after use. If you pass by reference, you can reuse the variable. And if you have something reading/writing to a variable, you cannot read/write to it somewhere else.
great vid! dropped a sub! i suck at programming but that python ball collision checker code physically hurt me to look at :> very relatable and im sure youll go far! that chess montage was very fun
9:12 your python code actually has a similar issue to the only running collisions on one ball at a time approach you tried in Rust just more subtle bug case 1: a ball runs into 2 other balls at the exact same tick and the first ball it gets processed with knocks it far enough that it isn't touching the second ball anymore causing that collision to not be processed bug case 2: a ball runs into a ball and the processed knock causes it to be "touching" an unprocessed ball causing a knock that shouldn't occur that tick
Rust has such a steep learning curve, but once you stop boxing with the compiler and borrow checker and end up dancing with it, it's such a wonderful experience. Once I got to that point (like 1 month of playing after a 5 month hiatus) I was the most productive I've ever been with code
3:25 that doesn’t edit the outer vector because you are passing by value. You are just editing the local copy. You have to pass a mutable reference to do that. vector& n
In Python you can specify the variable type almost the exact same way as in Rust: a_number: int = 5 Not that it does much of anything aside from helping with documentation.
10:40 You might try saving detected collisions to a list of collision events containing data about which balls to update and how to update them. Then you update one ball at a time using that data instead of copying the whole ball list.
Nice video! When implementing a spatial hash in bevy you should store the Entity instead of a components reference, this enables you to get back the entities components inside your system using the spatial hash Resource.
15:24 I'm surprised you didn't learn all of these concepts (except maybe memory safety but even that you learn by segfaults) when you went from Python to C++.
well, my way was: js + ts -> c++ (for really short period of time) -> rust i think the reason for this is mixed, it could be that c++ has very poor documentation? or its not really user friendly? c++ learning resources are not well structured? (see rust book, rust by example, rustlings, rust playground)
A usize is an unsigned integer whose size is defined to be the minimum size needed to store the highest possible addressable memory location for your machine. The thing about Rust is that at first a lot of things seem cumbersome, but when you stop to seriously ponder the question "what's the alternative?" you quickly realize that Rust is the best by far in most, if not all, categories.
Difference between String and &str(string slice) is its lifetime, effectively '&' in rust denotes we r using reference of the var, so in case of &str its lifetime depends upon original scope i.e. passing and returning gets difficult and sometimes not possible to use. There are further complications like not being able to use it later and so on as it is just reference to a string. Meanwhile 'String' type is a heap allocated variable that can be understood as normal working variable you would expect off of vars like int and so on. It is closer to String type from C++, also heap allocated memory need not be deallocated manually as it uses RAII. This is what i understand as a beginner, there definitely are some mistake but it is what i have understood
I have been blessed by the youtube algorithmic gods. My calling to learn the basics of yet another language that I hope I stick with and enjoy more than c++...
4:04 &str is a slice (a.k.a. fat pointer), which can point to any UTF8 string in memory, and it doesn't own the string to which it points. A String is an owned string. You can borrow it as a 4:08 &str to pass to functions, but you can also append to it, which may cause reallocation (like C++'s std::string).
Cool video! I really encourage people to spend some time in a functional language, maybe Scala, if they come from an OOP background before diving into Rust. Everything will make sense and you'll pick it up much faster.
Ah okay, so you are only just starting to learn how to program. That makes more sense. I would suggest you start with C and avoid all of the pitfalls of C++ or Rust. If you use Windows, then I'd suggest LCC-Win64 which provides an IDE and compiler and its compiler has extensions for operator and function overloading. That addition alone makes it worth the effort to use as a compiler even on Linux, because it runs perfectly with WINE. The easiest way to remember lifetimes with C is malloc() before you need it and only free() it when you're done with it. A simple way to handle that is if you are in a new scope and know you'll need something, go ahead and write two lines and insert new code between them: obj = malloc( ... ); free( obj ); and fill in the ... when you get to it.
I also did the python-to-rust switch for game development, it's been about 2 months and I love it, the typing is so much more explicit, I no longer lay awake at night fearing my integer is going to get turned into a float without my consent I suggest you try using bevy's event systems ASAP, they really change how you can make your systems interact, and they're a good way to handle mutating your components without conflict, I did mine like this: put player input event writers in pre-update, put all event readers in update, put game logic event writers in post-update, there's a ton of benefits to doing things this way that I won't get into in this comment, but it prevents systems mutating the same variable at the same time causing crashes, and guarantees that when systems are reading data, the data will not change during the frame, the data is essentially "still" while systems read it and decide how they want to change it my first components I wrote in bevy were Velocity, Acceleration, and Spin, it was a great study on how to work within the ECS
7:42 ECS is fast because you can split things up into smaller pieces. This allows you to only request the values you need for the operation that you are doing. Normally, if you were to put everything together (like a single massive enemy struct), this would work just fine put potentially you will have unnecessary values that don't get used often, filling your CPU cache, slowing it down. The key thing is that the CPU transfers in chunks (roughly 64 bytes at a time) and the time that it spends transferring values can be a significant bottleneck. So when you are working with large amounts of entities (like 1000+) you need to separate out the entities into smaller pieces to speed up the transfer time. If you are rendering to the screen, for example, you only need sprites and positions, you don't need the whole entity to do that. With ECS, you can split up entities into smaller parts to do these kind of heavy throughput while still relating the parts (components) to each other. If you are making a physics engine, you can have the physics engine only fetch colliders, and ignore all the other stuff that the entities might have.
4:50 if you have a variable named "index" it should almost always be a usize by default, e.g. `let index: usize = 0;` or `let index = 0usize;` usize is just the largest integer the local system can handle... it prevents a 32 bit system from trying to store a 64 bit integer
3:06 in python you can also use f strings instead of concatenation, so in this case it would have been f"the first part of the text {var1} the other part of the text" just like rust, and yes the f before the brackets is needed
A minor correction, there is no such a thing as a reference in Rust. At least not in the way you think of it from other languages. When you pass a parameter to a function you either let it borrow the value (meaning you want to keep control over it after the function call) or you give the function ownership of the value. Borrowing is not equivalent with pass by reference and give ownership is not equivalent with pass by value. Realizing this and accepting it is the key to truly unlocking the awesomeness of Rust.
They absolutely are references and are compiled as so. If you read the docs you'll see `&` and `&mut` referred to as references. Borrowing is what references do, not the other way around
10:18… is that O(n^2)? lmao. Can you make a grid where you check each square and see if balls collide that way? Splitting it up will I think make it O(nlogn) until you check for collisions at the edge of the grid, still better than checking every ball n times where n is the number of balls.
&str vs String in a nutshell: &str is a 'String' which you know how long it is, but don't know if you can write past the last byte of the &str. A String is also a 'String' which you know how long it is, but also have an extra field which we call capacity which lets you know how much more can you write to it
Well IMO if you hadn't that 4 month with C++ the 3 week on Rsut would turn to a six month at least and the result wouldn't satisfy you either because you hadn't that c++ implementation to compare to. Anyway great video👏
Getting pipebombed by bad ChatGPT rust code is I think the main thing slowing me down. Only when writing new code and forcing ChatGPT to comply can I get something useable. Remaking my main game in rust, 6k lines deep and I’m loving it now that I have a strong handle on rust concepts
I’d be interested to know why you put unreal engine as the most difficult engine on your graph? I guess your view is very programmer oriented as I think most people would associate unreal with being far more friendly than straight code like bevy or sfml.
I'm actually amazed you got this far... Bevy uses high level Rust concepts, that can do amazing stuff when utilized correctly. But hell I'm tinkering with Rust now almost two years and I still do not feel comfortable enough to play around with bevy beyond its examples. Learning Rust is already hard mode, learning Rust with Bevy earns you respect from the Doom Slayer...
2:44 BASED opinion. everybody hates on Rust syntax for some reason, but its so beautiful (or maybe I've been indoctrinated by the new testament, aka the rust programming language 2nd edition, and I am now too far gone. or maybe rust is just better ¯\(ツ)/¯ )
@@arjix8738 maybe... Tbh, i like the syntax where the type is on the right after the colon. it looks cleaner. I've also heard complaints about forming a string, where people complain that you have to use "String::from()" or "String::new()", but obviously they don't understand the beauty of Rust strings
3:23 That... should copy it... You have to specify a reference if you want to reference the vector, otherwise it's pass by value, and in C++, bindings own the objects they bind to.
10:08, I have my questions with this problem, this can be avoided by doing direct indexing, something like my_vector[I].collides(my_vector[j]), but I don't really know if this is the most optimal thing to do, this is how I always do it
Just a point, the fact that its now a struct type and not a class, in the end (even though C++ is touted to be more OOP hence the keyword class) they're literally the same kind of data structure, it doesn't really make a difference. Btw, no one using OOP languages is actually doing proper OOP arch, the concept of taking "real world concepts" and distilling them into a collection of classes is not what OOP is.
Your explanation about C++ references doesn't seem right to me. C++ Vectors aren't implicitly copied by reference and there is also a difference between a const and a mutable reference. References in C++ is just a convenient way to pass around a pointer that is known to be initialised, so technically you can also pass a reference using a pointer where you can pass a pointer to a constant. I have included some examples below for you: // Pass by value. void foo(std::vector vec) { //... } // Pass by mutable reference. void foo(std::vector& vec) { // ... } // Pass by mutable reference using pointer. void foo(std::vector* vec) { // ... } // Pass by const reference. void foo(const std::vector& vec) { // ... } // Pass by const reference using pointer. void foo(const std::vector* vec) { // ... } Passing by reference using pointers can also be confusing but a great explanation is from a Stackoverflow post describing: """ const char* is a pointer to a constant char char const* is a pointer to a constant char char* const is a constant pointer to a (mutable) char """ (source: stackoverflow.com/questions/162480/const-int-vs-int-const-as-function-parameters-in-c-and-c)
10:05 This is why I think it is better to learn C++ before Rust -> There is nothing inherently wrong about multiple mutable references, and your code would work just fine with them. Just use unsafe and assign mutable references anyway. So long as they don't alias (reference the same thing) it's perfectly fine in Rust. The reason they are avoided is for a couple of very rare edge cases. If you append to a vector, then the vector might move it's content elsewhere (to find somewhere with more room). If you set an enum, then the old value will be invalid. Both of these operations invalidate the old value, so this is the case where multiple mutable references can be unsafe. You could write to a region of memory that no longer exists. If you are not working with Enums or Vectors, then this problem is unlikely to occur. Your elements probably don't contain enums or vectors, so this problem can't happen. Rust is being over-protective. That said, you should be aware that the compiler can rewrite your code (to make it faster), so you should follow the borrow-checkers rules as much as possible, and ensure that mutable references do not alias.
jokes aside, you should think 10 times before using unsafe in Rust, its not C and not C++ where unsafe is everywhere and where you don't care that much about memory safety as we care in Rust
@@xshady2967 Being aware of when you can use unsafe is a good skill to have. Cloning the entire vector is a very bad idea- it involves a whole new heap allocation plus copying all the content. The only reason to use Rust over a garbage collected language is for performance. Safety matters to an extent, but if the end result is slower than a garbage collected language, you might as well use the garbage collected language and avoid the headache of the borrow checker. If you can stick to safe and still be reasonably efficient, then it's worthwhile doing so. I just don't see a good way to do that here. Maybe I am inexperienced though.
String is a data object internally represented as a Vec and is far more versatile. &str is a pointer to a string literal. They are much less flexible but faster. &String and str are almost never used. usize is an unsigned number the size of the systems pointer length.
to add ontop of that, string literals are &str and not String because Rust removes duplicate string literals, so you get a shared immutable reference this is an optimization done at compile time
10:30 Instead of copying the list of balls every time you want to check collisions, Why not use immutable references in your for loops and push the collision updates to a stack? Then after you've looped through each ball, you have a vector of the collision updates that need to be performed and can do all of them without needing 2 mutable references or having only half the balls have collision.
WHAT I DIDNT EXPECT THIS TO BLOW UP SO FAST.
But as many have pointed out, no matter how confident I sound, I’m still a noob at c++ and rust. So pls take everything shown with a massive grain of salt. And pls go easy on the criticism 😢
noob
@@TheSandwichCoder then get good first before spreading misinformation
@@TheSandwichCoder at least have the decency to run the code and check if it does what you expect it to do.
Damn chill
yea, lost me at words python + rust as c++ dev. anyway, great video, there is always a lot to be lerned.
Much of this reminds me of my experience learning rust.
It was painful at times battling the borrow checker.
But it has "unintentionally" taught me valuable lessons sometimes, how sometimes multiple mutable references can shoot you in the foot,
or forcing me to take a deeper thought to what data types I'm working with.
Sometimes though because of the borrow rules it's easy to take short cuts like cloning stuff willy nilly or unwrapping everything.
But over time your intuition will develop to know immediately what will work well in a rust way.
Welcome to the tribe :P
Some things I love about the language:
Enums (Every time I use another language I miss the way Rust Enums work), enforced naming convention(clean api's and for collaboration), excellent compiler error messages, Clippy (tool for giving you tips on improvements on your code)
TanTan! ;) can't wait for the next video to drop my friend.
whatever you're doing keep it up psst yes im subbed to you!
WTF MOM GET THE CAMERA 📷
I dont understand enums at all. How are they different from something like a tuple? Im chronically python brained i cant wrap my head around what a result enum is
@@Gigachad-mc5qz An enum *definition* is a set of valid types (or variants) a value CAN take on, but only takes one one of them. A simple example would be a boolean (in rust syntax):
enum bool {
False, // 0
True, // 1
}
instantiated as:
let a = bool::False;
Under the hood, these variants are just stored as integers ranging from 0 to the number of variants-1.
so `False as u32` would return 0.
Results are exactly the same way, except the `Some` variant holds a value
So a good way of thinking about them is booleans with more options (and extra steps depending on the language)
Hope this helps :)
@@radifireResults? i think you mean Options
this shit is hella well made for 69 subs
I thought he had a lot more, video is good quality
nice
nice
true
nice
something to note on your explanation at 3:32 - vectors and other containers are not automatically referenced when passed in cpp. this might also be a source of issues in your chess code. function parameters are ALWAYS passed by value (calling the copy constructor) and only referenced when you declare the type as a reference (const std::vector& myints). secondly i dont know what syntax you used for the cpp hashmap and linkedlist example at around the same time stamp - but those dont exist. not under those names anyway. a std::unordered_map is a hashmap-like container which also doesn't have the initialization syntax shown there.
good work though i understand the beginners trouble with cpp - nice end montage lol
Ohhh I see. Yeah for the hashmap and linked list I was too lazy to find the actual syntax so I just put some BS there. There was supposed to be a comment at the start that said I was too lazy but I guess it got dropped out 🤷. Thx for the advice though!
np man keep up the good work
6:43 bro complains about having to read documentation, what a world we live in
That's what I was thinking, too. ChatGPT is great for specific, hard-to-Google questions, but you should use it only as a way to orient yourself in the right direction, not try to have it do the work for you.
FR
It's a youtube video chief, relax
@@luyanda3746 why do you assume that I am incapable of making comments like this whilst being in a relaxed state?
I dont read docs, thats cringe
You put so much work into this video. 😭🙏 This guy deserves more subs.
9:34 you don't actually need mutable references, as you don't change elements but only read them, so shared references will do. However you can even get two mutable references to elements in one vector using split_at_mut method
What you mean. It does mutate the variable in the do_colision fn
4:40 i32 is a 32 bit integer that can be negative. u32 is a 32 bit integer that can't be negative, so unsigned integers are appropriate types for indexing. And usize is basically just a 32 or 64 bit unsigned integer, depending on whether or not your PC is 32 or 64 bit.
PS good video. It reminds me when I was younger and in a very similar stage, except Rust wasn't a thing at the time 😅. I went from Python - > C++ - > Rust as well. Something I will say is Rust basically forces you to learn concepts that C++ doesn't, but you can guarantee you'll shoot yourself in the foot if you don't understand them in C++. And good luck on your career 🙂
usize has the same bit width as a pointer* when we will need 128 pointers (which is probably not even close from now) usize will be 128 bits :)
@@darkfllame I doubt we'll ever use 128-bit addresses, the most we can put in a machine today is only 2^42. Some software actually narrows to 32 bits by storing indexes into a vector/arena and caching the high bits elsewhere - even if we needed more than 2^64 bytes addressable in some compute cluster, we would partition it into separate address spaces rather than widen everything.
However, usize is not necessarily enough to store a pointer. Rust pointers may contain a vtable/slice length, or even CHERI provenance information which is lost when casting to integer. It's enough to store pointer _offsets_ though, like in slice indexing.
Hey dude, awesome video!! I'm your thousandth subscriber, very well deserved. Please keep going 💪🏼
A "usize" is an unsigned integer with the size of the platform. Example: "usize" on a 64-bit CPU is an unsigned 64-bit integer. There is also "isize" which is the same but signed.
Other non-dynamic number types in Rust are:
u8
i8
u16
i16
u32
i32
u64
i64
u128
i128
f32
f64
They are pretty self-explanatory, examples:
u8 - Unsigned 8-bit integer. (AKA a byte.)
f64 - 64-bit floating point number.
i32 - Signed 32-bit integer. (Signed just means that it can also store negative numbers by giving up some of the larger unsigned numbers that it could store if it was unsigned.)
wait what would you need isize for
@raeplaysval Lol, good question.
@@raeplaysval pointer (or index) differences
This video is SO well made it’s actually crazy. It’s great that it blew up, because it deserves it
A `String` is a Boxed string allocated on the heap. This isn't a reference, we own these.
A &str` is a string slice; a string slice is a reference (non-null pointer) to an array of UTF-8 characters. We don't own the data referred to by the reference.
Rust has a string slice separate from a normal slice (`&[T]`) so we know that the data within is valid UTF-8.
I started learning Rust after C. For C, the first thing I did was read "The C Programming Language" by K&R. For Rust, I read "The Rust Programming Language." I don't remember programming in Rust being any more difficult to me than C. Maybe I've just got amnesia.
I have never had any success with LLMs. When I started learning programming (not that long ago) they didn't exist. Whenever I've tried to use them, they've only ever misled me and wasted my time. I know how to ctrl-f, however LLMs don't.
FYI: Rust is the programming language I'w most comfortable in.
To clarify, `String` is not the same as `Box`, while both are owned heap allocated `str`, the difference is that you can modify `String`'s length (you cannot do this with `Box`, though you can modify its content)
You probably don't remember it being hard because Rust isn't that hard coming from C. Arguably, it's even easier-a lot of the things you have to manually remember how to do in C (gotta love remembering to pass the length of an array alongside the actual array) are easier because of Rust's higher-level type system
Same bro coming from C, it does kinda feel familiar and comfortable. I would say C++ felt more different than C compared to rust.
Also thing of note is &str is a pointer+length
Bro your video editing is peak. 100/100. Claiming my OG ticket rn rn
The borrow checker is actually quite simple. If you put something into a function call by value, it deallocates the variable after use. If you pass by reference, you can reuse the variable. And if you have something reading/writing to a variable, you cannot read/write to it somewhere else.
great vid! dropped a sub! i suck at programming but that python ball collision checker code physically hurt me to look at :>
very relatable and im sure youll go far! that chess montage was very fun
9:12 your python code actually has a similar issue to the only running collisions on one ball at a time approach you tried in Rust just more subtle
bug case 1: a ball runs into 2 other balls at the exact same tick and the first ball it gets processed with knocks it far enough that it isn't touching the second ball anymore causing that collision to not be processed
bug case 2: a ball runs into a ball and the processed knock causes it to be "touching" an unprocessed ball causing a knock that shouldn't occur that tick
I was reading the comments when I read about your sub count, and checked it. I was sure you would at least have 120k subs
Rust has such a steep learning curve, but once you stop boxing with the compiler and borrow checker and end up dancing with it, it's such a wonderful experience. Once I got to that point (like 1 month of playing after a 5 month hiatus) I was the most productive I've ever been with code
3:25 that doesn’t edit the outer vector because you are passing by value. You are just editing the local copy.
You have to pass a mutable reference to do that.
vector& n
In Python you can specify the variable type almost the exact same way as in Rust:
a_number: int = 5
Not that it does much of anything aside from helping with documentation.
oh it helps a lot with code completion, particularly for function parameters
3:20 this is completely wrong. Variables in C++ are always passed by value unless the parameter’s type is a reference
I loved this. Great editing!
Nice stuff! Glad to see your subs 10xed at the time of this comment
10:40 You might try saving detected collisions to a list of collision events containing data about which balls to update and how to update them. Then you update one ball at a time using that data instead of copying the whole ball list.
Nice video! When implementing a spatial hash in bevy you should store the Entity instead of a components reference, this enables you to get back the entities components inside your system using the spatial hash Resource.
I was going to make fun of you for using python, then I saw you using Windows, so i'd rather make fun of you for that instead.
15:24 I'm surprised you didn't learn all of these concepts (except maybe memory safety but even that you learn by segfaults) when you went from Python to C++.
well, my way was: js + ts -> c++ (for really short period of time) -> rust
i think the reason for this is mixed, it could be that c++ has very poor documentation? or its not really user friendly? c++ learning resources are not well structured? (see rust book, rust by example, rustlings, rust playground)
A usize is an unsigned integer whose size is defined to be the minimum size needed to store the highest possible addressable memory location for your machine.
The thing about Rust is that at first a lot of things seem cumbersome, but when you stop to seriously ponder the question "what's the alternative?" you quickly realize that Rust is the best by far in most, if not all, categories.
I really recommend Lobster, it's a language that is statically typed, can compile and link with C++ and looks like python/C#
I also learned rust from a chess engine project, it's quite well suited for it
Yeah it helps a lot to learn the logic and basic functions 😎😎
please make more videos! you're amazing
Awesome video. Peak cinema. You'll have one billion subs in no time
And that’s how summer works as a guy! 😂
Memes aside - welcome to oxidation, and a phenomenal video btw
Congrats! Also great video !
Difference between String and &str(string slice) is its lifetime, effectively '&' in rust denotes we r using reference of the var, so in case of &str its lifetime depends upon original scope i.e. passing and returning gets difficult and sometimes not possible to use. There are further complications like not being able to use it later and so on as it is just reference to a string. Meanwhile 'String' type is a heap allocated variable that can be understood as normal working variable you would expect off of vars like int and so on. It is closer to String type from C++, also heap allocated memory need not be deallocated manually as it uses RAII.
This is what i understand as a beginner, there definitely are some mistake but it is what i have understood
75 subs? Let me fix that.
Keep up the good work
I have been blessed by the youtube algorithmic gods. My calling to learn the basics of yet another language that I hope I stick with and enjoy more than c++...
Enjoy more than C++? So anything except JS afaik
@iCrimzon youve got it down pat!
4:04 &str is a slice (a.k.a. fat pointer), which can point to any UTF8 string in memory, and it doesn't own the string to which it points. A String is an owned string. You can borrow it as a 4:08 &str to pass to functions, but you can also append to it, which may cause reallocation (like C++'s std::string).
Cool video! I really encourage people to spend some time in a functional language, maybe Scala, if they come from an OOP background before diving into Rust. Everything will make sense and you'll pick it up much faster.
Ah okay, so you are only just starting to learn how to program. That makes more sense. I would suggest you start with C and avoid all of the pitfalls of C++ or Rust. If you use Windows, then I'd suggest LCC-Win64 which provides an IDE and compiler and its compiler has extensions for operator and function overloading. That addition alone makes it worth the effort to use as a compiler even on Linux, because it runs perfectly with WINE. The easiest way to remember lifetimes with C is malloc() before you need it and only free() it when you're done with it. A simple way to handle that is if you are in a new scope and know you'll need something, go ahead and write two lines and insert new code between them: obj = malloc( ... ); free( obj ); and fill in the ... when you get to it.
1:16 jdh enjoyer spotted
Also great video, as a Rust and Bevy evangelical i approve
I also did the python-to-rust switch for game development, it's been about 2 months and I love it, the typing is so much more explicit, I no longer lay awake at night fearing my integer is going to get turned into a float without my consent
I suggest you try using bevy's event systems ASAP, they really change how you can make your systems interact, and they're a good way to handle mutating your components without conflict, I did mine like this: put player input event writers in pre-update, put all event readers in update, put game logic event writers in post-update, there's a ton of benefits to doing things this way that I won't get into in this comment, but it prevents systems mutating the same variable at the same time causing crashes, and guarantees that when systems are reading data, the data will not change during the frame, the data is essentially "still" while systems read it and decide how they want to change it
my first components I wrote in bevy were Velocity, Acceleration, and Spin, it was a great study on how to work within the ECS
7:42 ECS is fast because you can split things up into smaller pieces. This allows you to only request the values you need for the operation that you are doing. Normally, if you were to put everything together (like a single massive enemy struct), this would work just fine put potentially you will have unnecessary values that don't get used often, filling your CPU cache, slowing it down.
The key thing is that the CPU transfers in chunks (roughly 64 bytes at a time) and the time that it spends transferring values can be a significant bottleneck. So when you are working with large amounts of entities (like 1000+) you need to separate out the entities into smaller pieces to speed up the transfer time. If you are rendering to the screen, for example, you only need sprites and positions, you don't need the whole entity to do that.
With ECS, you can split up entities into smaller parts to do these kind of heavy throughput while still relating the parts (components) to each other. If you are making a physics engine, you can have the physics engine only fetch colliders, and ignore all the other stuff that the entities might have.
1:00 love the accent lol
4:50 if you have a variable named "index" it should almost always be a usize by default, e.g. `let index: usize = 0;` or `let index = 0usize;`
usize is just the largest integer the local system can handle... it prevents a 32 bit system from trying to store a 64 bit integer
Very well made video, Nice 👍👍
this is so cool
i wish i could edit like that xd
also, did you compile it in release mode? you get much better performance in release mode vs debug mode
I just got a new follower, please keep making new videos
this video is extemely well made i susbroobed
youre the sigma sandwichcoder thank you for the good content
3:06 in python you can also use f strings instead of concatenation, so in this case it would have been f"the first part of the text {var1} the other part of the text" just like rust, and yes the f before the brackets is needed
YEAH BUDDY LESS FOOKIN GOOO, I made as well engine on Rust, it was much easier for me than on C++ since I'm bad
1:26 "Goofy dies in a car crash while listening to sunflower " 💀💀💀💀
so proud of you pookie :33
really cool video man, keep it up
A minor correction, there is no such a thing as a reference in Rust. At least not in the way you think of it from other languages. When you pass a parameter to a function you either let it borrow the value (meaning you want to keep control over it after the function call) or you give the function ownership of the value. Borrowing is not equivalent with pass by reference and give ownership is not equivalent with pass by value. Realizing this and accepting it is the key to truly unlocking the awesomeness of Rust.
Ohhh yeah I heard those terms before but I didn’t really get what they meant. Thx though!
What about RC and Arc?
@@ValentinBaca No difference, you borrow or giving ownership of the RC(Arc) value. That they contains references is irrelevant for the discussion.
They absolutely are references and are compiled as so. If you read the docs you'll see `&` and `&mut` referred to as references. Borrowing is what references do, not the other way around
10:18… is that O(n^2)? lmao. Can you make a grid where you check each square and see if balls collide that way? Splitting it up will I think make it O(nlogn) until you check for collisions at the edge of the grid, still better than checking every ball n times where n is the number of balls.
4:43 A usize is an unsigned integer that is the same size as a CPU register, and is used for offsets and lengths.
subbed! good vid.
Also ye i subbed gefore 1k :)
You're great!
Honestly, amazing. Do you have a Discord server?
&str vs String in a nutshell:
&str is a 'String' which you know how long it is, but don't know if you can write past the last byte of the &str.
A String is also a 'String' which you know how long it is, but also have an extra field which we call capacity which lets you know how much more can you write to it
Well IMO if you hadn't that 4 month with C++ the 3 week on Rsut would turn to a six month at least and the result wouldn't satisfy you either because you hadn't that c++ implementation to compare to. Anyway great video👏
Nice video man. Thumbnail would've been way better using a white background and red and black text
Getting pipebombed by bad ChatGPT rust code is I think the main thing slowing me down. Only when writing new code and forcing ChatGPT to comply can I get something useable. Remaking my main game in rust, 6k lines deep and I’m loving it now that I have a strong handle on rust concepts
The thumbnail says you hate rust as much as I hate wayland.
I’d be interested to know why you put unreal engine as the most difficult engine on your graph? I guess your view is very programmer oriented as I think most people would associate unreal with being far more friendly than straight code like bevy or sfml.
I'm actually amazed you got this far...
Bevy uses high level Rust concepts, that can do amazing stuff when utilized correctly.
But hell I'm tinkering with Rust now almost two years and I still do not feel comfortable enough to play around with bevy beyond its examples.
Learning Rust is already hard mode, learning Rust with Bevy earns you respect from the Doom Slayer...
peak cinema
2:44 BASED opinion. everybody hates on Rust syntax for some reason, but its so beautiful (or maybe I've been indoctrinated by the new testament, aka the rust programming language 2nd edition, and I am now too far gone. or maybe rust is just better ¯\(ツ)/¯ )
@@_ethqnol_ I have converted to be a rustacean
I think most people "hate" it because it looks like typescript and other languages that have the type on the right
@@arjix8738 maybe... Tbh, i like the syntax where the type is on the right after the colon. it looks cleaner. I've also heard complaints about forming a string, where people complain that you have to use "String::from()" or "String::new()", but obviously they don't understand the beauty of Rust strings
6:14 it's compiling the engine too so it takes so long, after first compile it should just compile what you change.
1:26 Damn, I'm honored lol
Yeah just ignore the one below it 😂
@@TheSandwichCoderthick of it 😱
The reason you use usize to index into arrays is because it's pointer sized.
3:23 That... should copy it... You have to specify a reference if you want to reference the vector, otherwise it's pass by value, and in C++, bindings own the objects they bind to.
Time to try Nim if you want something Python-ish or Odin if you want something C-ish.
6:34 "ChatGPT gave me a lot of wrong information" you don't say 😄
4 nested for loops is crazy
10:08, I have my questions with this problem, this can be avoided by doing direct indexing, something like my_vector[I].collides(my_vector[j]), but I don't really know if this is the most optimal thing to do, this is how I always do it
I believe you could use an Arc to view the balls instead of making a snapshot
For me, the part I hate (and it's a deal breaker) from rust is only one mutable borrow at a time.
nice vid keep it up
Just a point, the fact that its now a struct type and not a class, in the end (even though C++ is touted to be more OOP hence the keyword class) they're literally the same kind of data structure, it doesn't really make a difference. Btw, no one using OOP languages is actually doing proper OOP arch, the concept of taking "real world concepts" and distilling them into a collection of classes is not what OOP is.
Now do this with the D. Simpler, cooler and it rocks sometimes.
14:50 rust is actually llvm-based so....
You know that coding in rust on windows is illegal, right?
this video earned my approval
btw im stupid yippee
Your explanation about C++ references doesn't seem right to me. C++ Vectors aren't implicitly copied by reference and there is also a difference between a const and a mutable reference.
References in C++ is just a convenient way to pass around a pointer that is known to be initialised, so technically you can also pass a reference using a pointer where you can pass a pointer to a constant. I have included some examples below for you:
// Pass by value.
void foo(std::vector vec) {
//...
}
// Pass by mutable reference.
void foo(std::vector& vec) {
// ...
}
// Pass by mutable reference using pointer.
void foo(std::vector* vec) {
// ...
}
// Pass by const reference.
void foo(const std::vector& vec) {
// ...
}
// Pass by const reference using pointer.
void foo(const std::vector* vec) {
// ...
}
Passing by reference using pointers can also be confusing but a great explanation is from a Stackoverflow post describing:
"""
const char* is a pointer to a constant char
char const* is a pointer to a constant char
char* const is a constant pointer to a (mutable) char
"""
(source: stackoverflow.com/questions/162480/const-int-vs-int-const-as-function-parameters-in-c-and-c)
this burns my eyes
13:59 after seeing those comments why do i feel like it was written by chatGpt 🤔🤔🤔🤔
I love how challenging it is. I feel like an idiot every time I use Rust, but I keep going because of my ego. Lmao
10:05 This is why I think it is better to learn C++ before Rust -> There is nothing inherently wrong about multiple mutable references, and your code would work just fine with them. Just use unsafe and assign mutable references anyway. So long as they don't alias (reference the same thing) it's perfectly fine in Rust.
The reason they are avoided is for a couple of very rare edge cases. If you append to a vector, then the vector might move it's content elsewhere (to find somewhere with more room). If you set an enum, then the old value will be invalid. Both of these operations invalidate the old value, so this is the case where multiple mutable references can be unsafe. You could write to a region of memory that no longer exists. If you are not working with Enums or Vectors, then this problem is unlikely to occur. Your elements probably don't contain enums or vectors, so this problem can't happen. Rust is being over-protective.
That said, you should be aware that the compiler can rewrite your code (to make it faster), so you should follow the borrow-checkers rules as much as possible, and ensure that mutable references do not alias.
"Just use unsafe and assign mutable references anyway" 🤣
jokes aside, you should think 10 times before using unsafe in Rust, its not C and not C++ where unsafe is everywhere and where you don't care that much about memory safety as we care in Rust
you better take a look at @HumanityAsCode comment
or comment by @seanp5524
@@xshady2967 Being aware of when you can use unsafe is a good skill to have. Cloning the entire vector is a very bad idea- it involves a whole new heap allocation plus copying all the content. The only reason to use Rust over a garbage collected language is for performance. Safety matters to an extent, but if the end result is slower than a garbage collected language, you might as well use the garbage collected language and avoid the headache of the borrow checker.
If you can stick to safe and still be reasonably efficient, then it's worthwhile doing so. I just don't see a good way to do that here. Maybe I am inexperienced though.
String is a data object internally represented as a Vec and is far more versatile.
&str is a pointer to a string literal. They are much less flexible but faster.
&String and str are almost never used.
usize is an unsigned number the size of the systems pointer length.
to add ontop of that, string literals are &str and not String because Rust removes duplicate string literals, so you get a shared immutable reference
this is an optimization done at compile time
I just did this like 2 months ago like the exact same transition
Helo Mr. im new here. good video. 👍
why not try raylib with rust??
6:12 There are faster linkers, like gold, and you can setup Bevy to be made a DLL while developing.
10:30 Instead of copying the list of balls every time you want to check collisions, Why not use immutable references in your for loops and push the collision updates to a stack? Then after you've looped through each ball, you have a vector of the collision updates that need to be performed and can do all of them without needing 2 mutable references or having only half the balls have collision.
awesome video, the music and sound effects are just WAY too loud
gooning to this :)