The Rust Survival Guide

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
there are so many programming languages you can potentially learn but options quickly evaporate when you start adding basic requirements if you're looking for a language that's general purpose gives you lowlevel control is memory safe and is extremely performant there's only one obvious choice rust the problem is many have tried to learn rust and many have failed along the way but all hope is not lost to survive your rust Learning Journey all you need is a clear understanding of a few Core Concepts welcome to the Russ Survival Guide where I'll cover the fundamental concepts of rust you need to know to Be an Effective rust developer more importantly I'll explain some of the weird unique and hard to understand parts of rust that tends to trip people up so you can write rust code with confidence instead of Rage quitting and reverting back to JavaScript in this video I'll cover the most fundamental and arguably the hardest to understand aspect of rust memory management specifically I'll explain how ownership borrowing and lifetimes work together to prevent catastrophic memory bugs while maintaining maining Peak Performance and how you can use these Concepts to effortlessly write idiomatic rust code instead of getting stuck fighting the borrow Checker now it's no secret that the Russ compiler is the Karen of all compilers screaming at you for every little mistake you make but this is for good reason you see traditionally memory management specifically memory management on the Heap has been done in one of two ways either by using a garbage collector that cleans up memory for you or by giving developers the ability to manually allocate any deallocate memory but these two approaches are problematic using a garbage collector results in too much runtime overhead for a systems programming language and manual memory management is simply too error prone trusting developers to manually write safe code is like giving a flamethrower to a toddler something is definitely getting burned down Russ takes a unique approach to memory management which solves both of these problems the rust compiler or more specifically a portion of the compiler known as the borrow checker analy izes your code and enforces a few simple rules at compile time that makes writing code which violates memory safety simply impossible and because these rules are enforced at compile time there is no runtime overhead resulting in the beautiful combination of Optimal Performance and memory safety the rules I'm referring to are known as the ownership and borrowing rules let's start with ownership there are only three rules the compiler enforces the first rule is that each value in Rust has an owner in this case the value is a Heap allocated string and its owner is the S1 variable the second rule is that there can only be one owner at a time so what happens when we create another variable and set it equal to S1 if we tried to print S1 we would get a compile time error stating borrow of moved value S1 this is because when we set S2 equal to S1 the value which is the Heap allocated string is moved into S2 and S1 is invalidated S2 is now the new and sole owner of the Heap allocated string if we wanted this code to compile we would either have to print S2 or we could explicitly clone S1 this will create a new Heap allocated string and assign it to S2 moving values also applies to functions in this case calling print string will move the Heap allocated string out of S2 and into the S argument of the print string function the third rule states that when the owner goes out of scope the value will be dropped scope simply refers to the region of code between curly brackets when our Heap allocated string is created its owner S1 is in main scope then ownership is transferred to S2 which is still in main scope finally ownership is transferred to the S argument of the print string function at this point the owner of the Heap allocated string which is the S argument is inside the print string function scope at the end of the function s goes out of scope and the value which is a heap allocated string is dropped or deallocated or cleaned up whichever word you want to use as you can see rust's ownership rules ensure that memory is automatically cleaned up without the need of a garbage collector but while the ownership system provides a solid foundation for memory safety there are often cases where we want to access data without transferring ownership for example let's say we wanted to call Print string twice this results in an error because the first time we call Print string S1 is moved into the print string function and then dropped at the end of the function scope one way we can solve this problem is by cloning S1 but that requires extra memory allocation which is not ideal another solution would be for the print string function to take ownership of a string print it out and then give ownership of that string back to the caller by returning it but this is also not ideal we don't want to be returning values from a function just to appease the ownership rules what we really want is a way to access data without transferring its ownership and this can be done by using references instead of the print string function taking a string as an argument we'll change it to take a reference to a string and update the print string call sites to pass in a reference instead of an owned value in our code is now compiling we can further improve this code by making the print string function except a string slice if you're not familiar with string slices I made an entire video about strings in Rust which I'll link in the description creating references in rust is called borrowing because metaphorically the reference borrows access to the data from the owner without taking ownership itself similar to how when you borrow something in real life you're temporarily using it without taking permanent possession of it and just like owned values must abide by the ownership rules borrowed values must abide by the borrowing rules and there are only two the first rule is that at any given time you can either have one mutable reference or any number of immutable references to demonstrate this we'll create two variables holding references to the Heap allocated string both of these references or borrows in ruste are immutable which means we're not violating rule one however if we made one of these variables a mutable reference the rust compiler would throw an error this may seem like a strange limitation but it ensures that if the value is being mutated no other part of the code can be reading or modifying it at the same time to illustrate why this could be a problem let's look at a more realistic example here we have a variable called X which stores a vector of numbers we then save the last element in the vector push a new element onto the vector and finally print the saved last element this code will not compile due to rust borrowing rules why is that when we call the last method we get an immutable reference which we later use to print out the last element in the vector but in the middle of using this immutable reference we call the push method which takes a mutable reference to self this overlapping use of immutable and mutable references is problematic when you push a new element onto the vector it may need to reallocate the underlying memory to accommodate the new element if it does reallocate the memory our reference to the last element will be pointing to deallocated memory leading to undefined behavior when we try to print it out luckily the rust compiler prevents us from making this mistake and the fix is straightforward we can simply move the call to push up one line so that the mutable reference or borrow doesn't overlap with the immutable borrow the second borrowing rule is that references must always be valid in this example we create the variable R without assigning a value to it then inside an inter scope we create a heap allocated string and set R to be a reference to that string finally outside the in scope we print out R this is a problem because s will be deallocated at the end of the inner scope at which point R would be pointing to invalid memory luckily the borrow Checker rejects this code with an error stating borrowed value does not live long enough the way the borrow Checker determines if the borrowing rules are being followed is by looking at lifetimes a lifetime refers to the span of the program during which a specific piece of data is valid the borrow Checker evaluates the lifetimes of values to ensure the ownership rules are being followed to check whether the reference is valid the borrow Checker ensures that the reference's lifetime does not outlive the owned values lifetime in this case the lifetime of the owned value s ends at the end of the inner scope while the lifetime of the reference extends beyond the inner scope because we pass it to the print line macro to fix this we can simply remove the inner scope now the lifetime of s extends to the end of the main function and the reference we pass into the print line macro is valid the second borrowing rule ensures memory safety by preventing you from accidentally accessing deallocated or uninitialized memory all without introducing runtime overhead in this example it was easy for the borrow Checker to evaluate lifetimes to determine if the code was problematic but that's not always the case sometimes we need to help the borrow Checker understand relationships between lifetimes take a look at this examp example we create two strings in main we then create a variable called result which is equal to the return value of calling the longest function the longest function takes two string slices and returns a reference to the longest string which we then print out let's think about this code from the point of view of the borrow Checker as the borrow Checker how would you know if result is not a dangling reference when we try to print it out remember the borrow Checker checks the lifetime of references to see if they are valid or not but what is the lifetime of results well the longest function returns a reference but as the borrow Checker we have no idea what the lifetime of that reference is remember that the borrow Checker runs at compile time and at compile time we don't know if a or b is going to be returned that decision will be made at runtime we also don't know the concrete lifetimes of A or B because again that will be determined at runtime this is a situation where we have to help the compiler reason about the lifetimes of references and the borrow Checker lets us know this by throwing an error which States missing lifetime specifier to fix this error we have to understand the difference between concrete and generic lifetimes so far we've seen concrete lifetimes a concrete lifetime is the span of code where a value is valid but when we Define functions or structs that operate on references we don't know the exact lifetimes of those references at compile time so instead we use generic lifetime annotations to express relationships between lifetimes ensuring the function or struct can work with references of any valid lifetime going back to our example to Aid the compiler will specify a relationship between the lifetime of a b and the return Value First we'll Define a generic lifetime called a generic lifetime parameters or annotations in Rust are prefixed by a tick then we'll assign that lifetime to a b and the return value we've just created a relationship between the lifetime of a b and the return value the relationship states that the lifetime of the returned reference will be equal to the shortest lifetime passed into the function with this information the borrow Checker is now able to properly evaluate our code for memory issues in this example the lifetimes of the reference to S1 and the reference to S2 both continue on until the end of the main function which means we can print result without any issues but let's see what happens when the lifetime of references differ in this example S2 is created inside an inner scope the lifetime of the reference to S1 still extends to the end of the main function however the lifetime of the reference to S2 ends at the end of the inner scope remember that the lifetime of the result variable is equal to the shortest lifetime passed in which means the lifetime of result also ends at the end of the inter scope printing result after the inner scope ends is problematic but luckily the borrow Checker catches this issue and throws an error with this fundamental understanding of memory management in Rust you're well set up for the rest of your rust Learning Journey there's a lot lot more to cover on memory safety things like common compile time errors and how to fix them or smart pointers unfortunately that would make this video way too long but if you guys want to see follow-up videos on these topics let me know in the comments section below before you go make sure to get your free rust cheat sheet at let's ry.com sheet give this video a like And subscribe to the channel for more rust content hope you've enjoyed the video and remember to stay Rusty
Info
Channel: Let's Get Rusty
Views: 133,104
Rating: undefined out of 5
Keywords: Rust Programming, Rust Language, Rust Tutorial, Rust Basics, Rust Concepts, Rust Lifetimes, Rust Borrowing, Rust Ownerships, Rust Memory Management, Rust Coding, Programming Tips, Coding Tutorial, Learn Rust, Rust Best Practices, Rust for Beginners, Rust Video Tutorial, Rust Programming Challenges, Rust Language Tutorial, Rust Video Lessons, Rust Learning Resources, Rust Programming Techniques, Practical Rust Tips, Rust Development Strategies, Rust Coding Techniques
Id: usJDUSrcwqI
Channel Id: undefined
Length: 12min 33sec (753 seconds)
Published: Sat Nov 11 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.