If you ever had any problems in your code with dangling pointers you don't miss 'em. I'll stay happy with the little Box and be even happier I never have to deal with that mess. What you won't use in this is the List enum because linked lists are hot garbage and there is absolutely zero use case except for some kludge-y recursive algorithms. Using Box with Vec is however the best way to use it. We all have growable Vec's that we need to pass between bits of code by reference. I think linked list information really should be included in another topic specifically on recursion where it is most likely to be used, because for the rest of the programming you ever do you just won't care about it or use it.
Seems like you could also get away with using Option right? Is Option a smart pointer type as well since it can be None? EDIT: Nope. Option needs to know the size of T
I know that you are calling this a "cons list" but this is just an old school linked list. Cons might be from lisp, but linked lists are language agnostic
it's not an old school linked list. linked list consists of a accessory struct and nodes, whereas each node points to the next one. in a cons list, each list points to another or no list
@@dagoberttrump9290 a cons list points to another cons list or no list. so exactly like a classical linked list... (obviously a node points at another node, but structurally another node is just the start of another linked list). i think its just semantics at this point.
Why we need the use statement at 6:43? Assuming an enum is like a type, what does it mean "to bring a type into scope", and why isn't it already in scope right after being defined?
Here "to bring a type into scope" means bringing the variants of enums into main scope. i.e. you can write it like: "Variant" instead of "Enum::Variant"
@@flippert0 I know, but thats not exactly what I asked. As far as I understood the original comment "could a & reference be used instead of Box?", it referred to the Linked List example in the video, so the question is "could linked list be written in Rust using references, not Boxes". Its not immediately obvious that you need to keep ownership, so just brushing it off with "you cannot transfer ownership with a reference" does not answer the question. Maybe I dont need to transfer ownership. Maybe I do, but its not obvious.
@@RenderingUser a datatype that only allocates space to hold one of its members. Example: union Foo { i32 x, i64 y, i128 z, } is 128 bits long The difference between rust enum and union is that at runtime the information which one is active is not available in a union and is often stored alongside in a separate field or manually kept track of by the developer.
It doesn't always need extra space. For example, if you call std::mem::size_of::() it returns 8. The compiler uses null pointer optimization and uses 0 to represent the 'None' option and any other value to represent the 'Some' option so a tag field isn't needed. It can't do this with eg. Option because all values of u64 are valid, so the size is 16 bytes (8 bytes for u64, 1 byte for tag, and 7 bytes padding for alignment). It can do it for pointer-like types (eg. &T, &mut T, Box, function pointers) and numerical types where 0 isn't a valid representation, eg. core::num::NonZeroU64. size_of::() == 8 size_of::() == 16
I found this part of the book confusing :(, in the book it doesn't show that rust knows list is of type List, but in your VScode we can see the type, but it's not clear how it was able to infer the type. Why it doesn't treat list as "Cons" for example?
What is the point of deallocating Box(5) when the program terminates??? Isn't the entire stack and heap automagically deallocated at program termination?
If you have a program which runs for a long time and has lots of allocations, this is called a memory leak. Over time the entire system memory gets taken up by data which isn't accessed by anyone until the system no longer has any memory to hand out which is very bad.
I really liked your explanations. I just have a question about the recursive list structure. You mentioned at the end of the video that line 9 is not very readable. It got me thinking if there would be any other nicer way of making it more readable but using the same recursive data structure approach. Do you have any idea in mind? keep rolling the videos!! :)
Well you could define a little helper function like this: fn cons(i: i32, list: List) -> List { List::Cons(i, Box::new(list)) } and then construct the list like this: let list = cons(1, cons(2, cons(3, Nil)));
You could also define a helper function that adds one layer, like: fn prepend(l: List, value: i32) -> List { Cons(value, Box::new(l)) } let mut l = Nil; l = prepend(l, 5); l = prepend(l, 7);
But what if there are so many pointers and structures even on heap - what will be result of this situation? out of memory error? Panic? Process will be killed by os? What happens? And how set a limit for number of pointers and enums instances? For example if i want to set 100 limit and set it in "enum logic" or some structure what would wrap enum. Sorry for my English
If allocation fails, rust program just terminates with out of memory error. The program can't panic, because panic needs to drop all values and drops may allocate memory (which we don't have). To limit the number of items in the list, you need some struct that keeps track of the length, and you need the list to be a private field of the struct, so it can't be accessed by the user directly. The creation and modification of the list needs to happen through the methods of the struct, so it can enforce the maximum capacity with custom logic.
As illustrated by the example in the video, Box is useful when the size of the data cannot be known at compile time, but a fixed size structure is required by the compiler (as in recursive structures). Now, you could use regular references in these scenarios as well, making Box redundant, but there is a key difference: Box owns the data it points to, while regular references do not. So with references, you would need to specify lifetime parameters to make sure that the reference is valid throughout your program. Box is convenient as it handles heap allocation and deallocation automatically based on variable scopes and as it implements the Deref trait, can be used as value directly.
@@ChumX100 that makes sense, but if using smart pointers is more convenient option, what are the downsides compared to lifetimes? Is it a good idea to just use smart pointers and forget about explicit lifetime annotations? Rust could use it internally, with some syntax sugar, and not bother users with lifetimes, but it doesn’t… I guess because smart pointers are real structs and require additional memory allocation?
This (and the book) bugged me because I wanted to print the list. let list = Cons(2, Box::new(Cons(3, Box::new(Nil)))); print!("["); let mut item = list; let mut first = true; loop { let y = match item { List::Cons(a, next) => { item = *next; a } List::Nil {} => { println!("]"); break; } }; if !first { print!(", "); } print!("{}", y); first = false; }
It's not. It's just rust being rust. Sometimes the book is frustrating to read because of indirection like this. 90% of the examples used are not applicable to anything.
why do you keep copying/pasting examples from the book ? come up with your ones(better ones). To me, it seems like whatever book says, it's mentioned in these videos.
📝 Get your *FREE Rust cheat sheet* : www.letsgetrusty.com/cheatsheet
The following code works:
#[derive(Debug)]
enum List
Please cover RefCell :)
Coming up!
Golang beginners - Pointers are so hard to understand
Rusty Bogdan - Regular pointers aren't cool enough
so true
If you ever had any problems in your code with dangling pointers you don't miss 'em. I'll stay happy with the little Box and be even happier I never have to deal with that mess. What you won't use in this is the List enum because linked lists are hot garbage and there is absolutely zero use case except for some kludge-y recursive algorithms. Using Box with Vec is however the best way to use it. We all have growable Vec's that we need to pass between bits of code by reference. I think linked list information really should be included in another topic specifically on recursion where it is most likely to be used, because for the rest of the programming you ever do you just won't care about it or use it.
You do a great job in explaining these things. Thank you for the videos.
Congrats! You are half way through the tutorial series! Keep up the momentum!
Thanks for the great videos bogdan!
this comment actually made me very happy, thanks!
Didn't even want to learn Rust... But, the presentation and bite sized nature is easily digestable!
Seems like you could also get away with using Option right? Is Option a smart pointer type as well since it can be None?
EDIT: Nope. Option needs to know the size of T
Option perhaps.
I know that you are calling this a "cons list" but this is just an old school linked list. Cons might be from lisp, but linked lists are language agnostic
it's not an old school linked list. linked list consists of a accessory struct and nodes, whereas each node points to the next one. in a cons list, each list points to another or no list
@@dagoberttrump9290 a cons list points to another cons list or no list. so exactly like a classical linked list... (obviously a node points at another node, but structurally another node is just the start of another linked list). i think its just semantics at this point.
@@dagoberttrump9290So a linked list? You don't need to wrap a node pointing to another node in a struct to call it a linked list.
A very clear explanation and a good example. Thanks for the video!
i have gotten a lot out of your videos thanks dude!!
That was well explained, thank you.
Thanks for what your do.
Why we need the use statement at 6:43? Assuming an enum is like a type, what does it mean "to bring a type into scope", and why isn't it already in scope right after being defined?
Here "to bring a type into scope" means bringing the variants of enums into main scope. i.e. you can write it like: "Variant" instead of "Enum::Variant"
@@dulanchampa Thanks!
Interesting! But now I'm wondering: could a & reference be used instead of Box? What are the upsides to using Box in this example?
Nope, you can't allocate new memory and save it on a reference, you need a box to do it
@@someon3 yeah, but why not? What is the reason?
@@lizzienovigotbecause... That's... How it's made?
@@lizzienovigot You cannot transfer ownership with a normal reference.
@@flippert0 I know, but thats not exactly what I asked. As far as I understood the original comment "could a & reference be used instead of Box?", it referred to the Linked List example in the video, so the question is "could linked list be written in Rust using references, not Boxes". Its not immediately obvious that you need to keep ownership, so just brushing it off with "you cannot transfer ownership with a reference" does not answer the question. Maybe I dont need to transfer ownership. Maybe I do, but its not obvious.
Learning and enjoying your videos 👍 I think none would be the proper syntax here. No nils in Rust
THE RUST GANG!!
more usefull use of Box is using traits, when you just store object as trait
Awesome! Keep it up! Hope you will soon switch to 2 videos per week :)
O wow I just realized that a rust enum is basically a union
What's a union?
@@RenderingUser a datatype that only allocates space to hold one of its members.
Example:
union Foo {
i32 x,
i64 y,
i128 z,
}
is 128 bits long
The difference between rust enum and union is that at runtime the information which one is active is not available in a union and is often stored alongside in a separate field or manually kept track of by the developer.
Doesn't an enum need additional memory to specify which of the variants is actually stored?
It doesn't always need extra space. For example, if you call std::mem::size_of::() it returns 8. The compiler uses null pointer optimization and uses 0 to represent the 'None' option and any other value to represent the 'Some' option so a tag field isn't needed.
It can't do this with eg. Option because all values of u64 are valid, so the size is 16 bytes (8 bytes for u64, 1 byte for tag, and 7 bytes padding for alignment).
It can do it for pointer-like types (eg. &T, &mut T, Box, function pointers) and numerical types where 0 isn't a valid representation, eg. core::num::NonZeroU64.
size_of::() == 8
size_of::() == 16
I found this part of the book confusing :(, in the book it doesn't show that rust knows list is of type List, but in your VScode we can see the type, but it's not clear how it was able to infer the type. Why it doesn't treat list as "Cons" for example?
What is the point of deallocating Box(5) when the program terminates??? Isn't the entire stack and heap automagically deallocated at program termination?
If you have a program which runs for a long time and has lots of allocations, this is called a memory leak. Over time the entire system memory gets taken up by data which isn't accessed by anyone until the system no longer has any memory to hand out which is very bad.
Amazingly good content!
Thank you.
I really liked your explanations. I just have a question about the recursive list structure. You mentioned at the end of the video that line 9 is not very readable. It got me thinking if there would be any other nicer way of making it more readable but using the same recursive data structure approach. Do you have any idea in mind? keep rolling the videos!! :)
Well you could define a little helper function like this:
fn cons(i: i32, list: List) -> List {
List::Cons(i, Box::new(list))
}
and then construct the list like this:
let list = cons(1, cons(2, cons(3, Nil)));
You could also define a helper function that adds one layer, like:
fn prepend(l: List, value: i32) -> List {
Cons(value, Box::new(l))
}
let mut l = Nil;
l = prepend(l, 5);
l = prepend(l, 7);
thanks so much!
Isnt that just a link list and if not what is the difference 🤨
7:20 getting flashbacks from functional programming pls put a trigger waring there xd
But what if there are so many pointers and structures even on heap - what will be result of this situation? out of memory error? Panic? Process will be killed by os? What happens? And how set a limit for number of pointers and enums instances? For example if i want to set 100 limit and set it in "enum logic" or some structure what would wrap enum. Sorry for my English
If allocation fails, rust program just terminates with out of memory error. The program can't panic, because panic needs to drop all values and drops may allocate memory (which we don't have).
To limit the number of items in the list, you need some struct that keeps track of the length, and you need the list to be a private field of the struct, so it can't be accessed by the user directly. The creation and modification of the list needs to happen through the methods of the struct, so it can enforce the maximum capacity with custom logic.
@@KohuGaly Box also has a try_new that allows you to gracefully catch allocation errors
How to impl a iterator for this emum?
Stupid question, but why do your VSCode suggestions have a benzene molecule?
Tabnine I think
It's an AI based tool
Github copilot wasnt available 2 years ago. TabNine was a really good AI-based suggestion tool
Keep going bro
Isn't a pointer to a small object, such as ints and chars, just as expensive as the object itself?
It is.
In fact, on a 64 bit architecture, a pointer (8 bytes) is larger than an i32 (4 bytes)
I don't really understand the cases of using smart pointer, particularly Boxes ... Even though the explanation of writing the code is clear.
As illustrated by the example in the video, Box is useful when the size of the data cannot be known at compile time, but a fixed size structure is required by the compiler (as in recursive structures). Now, you could use regular references in these scenarios as well, making Box redundant, but there is a key difference: Box owns the data it points to, while regular references do not. So with references, you would need to specify lifetime parameters to make sure that the reference is valid throughout your program. Box is convenient as it handles heap allocation and deallocation automatically based on variable scopes and as it implements the Deref trait, can be used as value directly.
@@ChumX100 Thanks man! I got it when i checked again
@@ChumX100 that makes sense, but if using smart pointers is more convenient option, what are the downsides compared to lifetimes? Is it a good idea to just use smart pointers and forget about explicit lifetime annotations?
Rust could use it internally, with some syntax sugar, and not bother users with lifetimes, but it doesn’t… I guess because smart pointers are real structs and require additional memory allocation?
The Nil variant in my project is 4 bytes ☹
4:43
This (and the book) bugged me because I wanted to print the list.
let list = Cons(2, Box::new(Cons(3, Box::new(Nil))));
print!("[");
let mut item = list;
let mut first = true;
loop {
let y = match item {
List::Cons(a, next) => {
item = *next;
a
}
List::Nil {} => {
println!("]");
break;
}
};
if !first {
print!(", ");
}
print!("{}", y);
first = false;
}
kewl
How is this different from a linked list?
It's not. It's just rust being rust. Sometimes the book is frustrating to read because of indirection like this. 90% of the examples used are not applicable to anything.
I always get this doubt are you saying "the rust programming language " or "the best programming language " ?
Not that great explanation because cargo check prmoted not only box, but also rc, so it wasnt clear why you didnt use rc instead.
Anyone from the future still here
why do you keep copying/pasting examples from the book ? come up with your ones(better ones). To me, it seems like whatever book says, it's mentioned in these videos.
The title of the video series is "The Rust Lang Book", so he's basically going over whatever is in the book.