Crust of Rust: Smart Pointers and Interior Mutability

  Рет қаралды 118,374

Jon Gjengset

Jon Gjengset

Күн бұрын

In this fourth Crust of Rust video, we cover smart pointers and interior mutability, by re-implementing the Cell, RefCell, and Rc types from the standard library. As part of that, we cover when those types are useful, how they work, and what the equivalent thread-safe versions of these types are. In the process, we go over some of the finer details of Rust's ownership model, and the UnsafeCell type. We also dive briefly into the Drop Check rabbit hole (doc.rust-lang.org/nightly/nom...) before coming back up for air.
This is definitely a more technically advanced stream than some of the earlier ones, and may be a little harder to follow. I apologize for that in advance! Please do leave questions here or on Discord and I'll try to help explain what's going on.
You can find the final code at gist.github.com/jonhoo/7cfdfe... and the Discord at / discord
0:00:00 Introduction
0:01:11 Discord
0:02:31 Agenda
0:03:50 Interior Mutability
0:07:47 Cell
0:23:39 Trying to Test Cell
0:40:17 UnsafeCell
0:41:21 RefCell
0:54:21 RefCell Smart Pointer
1:06:27 Rc (reference counted ptr)
1:23:49 NonNull
1:31:55 PhantomData and Drop Check
1:44:25 ?Sized Briefly
1:47:30 Thread Safety
1:54:20 Copy-on-Write (Cow)
You can watch the live version with comments at • Crust of Rust: Smart P...

Пікірлер: 201
@derekdreery
@derekdreery 4 жыл бұрын
These are really great, and they fill the "intermediate" gap in rust tutorials, which is very valuable!
@jonhoo
@jonhoo 4 жыл бұрын
Thanks, I'm glad you think so!
@dmitrij34
@dmitrij34 4 жыл бұрын
@@jonhoo , really enjoy the content. A little question, if you don't mind... I've just recently started to check out Rust, so sorry if it's a dumb question - your implementation of the Cell, wouldn't it leak memory on .Set(...)? Who is responsible for the cleanup? I doubt unsafe cell would do that. Are we effectively throwing away the raw pointer?
@jonhoo
@jonhoo 4 жыл бұрын
Ah, no, not quite. `set` overwrites the old value through a `&mut T` (that's what the dereference of the raw pointer we get back from `UnsafeCell` produces). When you change a value through a `&mut T`, Rust automatically drops the old value for you :)
@dmitrij34
@dmitrij34 4 жыл бұрын
@@jonhoo Interesting... From what I've heard in a lot of the Rust tutorials I've assumed so too. And then I've took a look at the Cell implementation of the Cell in the standard library: pub fn set(&self, val: T) { let old = self.replace(val); drop(old); } For some reason drop here is explicitly called. As I understand, the replace returns T, and it should be automatically destroyed when the scope ends, but why drop then? Took the code from here: doc.rust-lang.org/src/core/cell.rs.html#344
@jonhoo
@jonhoo 4 жыл бұрын
That's actually for a slightly different reason - the standard library Cell has a replace method, and rather than copying the unsafe code from replace into set, they re-use replace in set. In which case they also need to deal with the return value. They don't need the explicit drop there though, that's probably just for exposition.
@ChronosWS
@ChronosWS 4 жыл бұрын
~30:00 I very much appreciate that writing a failing test to show concurrent access leads to inconsistent results is hard - which is exactly what makes concurrent programming in general hard, and why Rust is so nice for making concurrent code that works.
@Knirin
@Knirin Жыл бұрын
The test worked but not in the way he expected it to. The allocation was occurring separately from the mutation of the UnsafeCell’s internal value. Which array pointer made it into the UnsafeCell last changed between test runs.
@chsblue2
@chsblue2 2 жыл бұрын
A cow literally started mooing outside when you began to explain Copy On Write. Thank you for sharing your knowledge. Very helpful!
@nikis05
@nikis05 4 жыл бұрын
My favourite part is: “why is this working?? Great, it failed!” :D Jokes aside, thanks for an amazing tutorial!
@GenusvProgramming
@GenusvProgramming 4 жыл бұрын
Thank you for this! it was really helpful. I love this Crust of Rust series, I for sure will have to watch it a couple of times.
@bkaankose
@bkaankose 3 жыл бұрын
This is one of my best videos of your collection here. Thank you for doing advanced stuff. Even though Cell might seem like a structure that don't have a lot of use cases, it helped me to understand a lot of intermediate concurrency and threading problems in programming.
@manjunath3929
@manjunath3929 4 жыл бұрын
I had spent hours to understand smart pointers but this video helped me to understand in greater depth than I could reading docs. You are really gift to rust community and people like me who are new to rust. Thanks a lot Jon
@nilshaberstroh2705
@nilshaberstroh2705 3 жыл бұрын
Delicious content. The way you explained these pointer types makes it actually possible to distinguish and remember them. Very very valuable video.
@beastle9end499
@beastle9end499 2 жыл бұрын
I recently switched from C++ to Rust and that type of content is exactly what I was looking for. Thanks very much:)
@luctielen
@luctielen 3 жыл бұрын
Thanks, this video in the series especially explained a lot of things that were previously unclear to me before. Kudos!
@christopher8641
@christopher8641 Жыл бұрын
This video has been a godsend for understanding some of these concepts. You are a valuable teacher! Thanks for these vids, they have really pushed my understanding forward by quite a bit
@sedat4842
@sedat4842 3 жыл бұрын
I have been looking for some intermediate/advanced tutorials on Rust, but wasn't lucky so far. Unfortunately, most talks in conferences are introductory but hese videos are great for me to continue learning. Thanks a lot Jon! Also, I donate monthly to GiveDirectly and I recommend everyone to do so.
@Codeaholic1
@Codeaholic1 2 жыл бұрын
One of the better simple explanations of PhantomData and the drop check.
@VivekYadav-ds8oz
@VivekYadav-ds8oz 2 жыл бұрын
Those two questions about taking a &mut and storing it, and modifying directly through the NonNull ptr rather than using a Cell, are what I had in my mind too! That's why you should ask questions if you're live, you don't know who else might be thinking about that too.
@sortof3337
@sortof3337 Жыл бұрын
The bad example with bad array consistently fails on my older machine but doesn't fail on new one. These tutorials are so vaulable and insightful. I have literally learned so much from your videos. tysm.
@hscowef4662
@hscowef4662 4 жыл бұрын
Very nice video, I was struggling to understand unsafe but watching this after I saw someone share it in the Rust Community Discord server helped me a lot thank you :)
@kevincarvalhodejesus4473
@kevincarvalhodejesus4473 2 ай бұрын
That's the best rust channel on youtube. I really love such deep and explanatory videos. I been watching these series and now i feel like rust is starting to click on my mind. Thank you so much Jon for such a great content!
@jonas-mm7em
@jonas-mm7em 2 жыл бұрын
Thanks Jon for the great video content once again. It's really interesting and gives concrete examples of harder to grasp Rust notions.
@sputnick1
@sputnick1 6 ай бұрын
This has got to be the most comprehensive explanation of rust’s smart pointer types ❤
@chaudharypraveen98
@chaudharypraveen98 Жыл бұрын
Awesome. Your way of explanation is magnificent. Thanks for the making tutorial.
@monawoka97
@monawoka97 2 ай бұрын
These are seriously university quality lectures. I cannot tell you how much I appreciate you making all these!
@Kodlak15
@Kodlak15 Жыл бұрын
Thank you for the time you have put into this series. It’s been remarkably helpful! My brain is overheating a bit after this one 😅
@artyomostrikov
@artyomostrikov Жыл бұрын
Thanks for a nice video that gives important knowledge on a really vital topics with examples! I hadn't known some things exist in Rust until watching these videos.
@WindBringsMemories
@WindBringsMemories 3 жыл бұрын
Great tutorial! Liked the debugging part as well :)
@billjohnson6863
@billjohnson6863 3 жыл бұрын
Super useful! Thanks for doing these.
@ChristopherBreeden85
@ChristopherBreeden85 3 жыл бұрын
I enjoy how Jon keeps trying to write incorrect code to show why it's incorrect but is continually stopped by the compiler :)
@troglodytto
@troglodytto 8 ай бұрын
Rust compiler is beautiful in that sense. Literally forces you to write correct code.
@karthiknedunchezhiyan1171
@karthiknedunchezhiyan1171 3 жыл бұрын
Really enjoyed the video, keep going!
@eddieh7962
@eddieh7962 2 жыл бұрын
This is so helpful! I knew there were tons of places in rust code where I got yelled at because of having two mutable references, even if those references never existed at the same time in a single threaded app. I guess I can use Cell for situations like this!
@logicprojects
@logicprojects 4 жыл бұрын
Great work, Thanks for all you do
4 жыл бұрын
Looking forward to the the continuation on mutex and rwlock, great stuff
@catalinneagu1417
@catalinneagu1417 2 жыл бұрын
You're a really good teacher. Thank you. Also donated
@nammari4276
@nammari4276 Жыл бұрын
Incredible value. I love these!
@alexanderadhyatma3126
@alexanderadhyatma3126 4 жыл бұрын
Thank you Jon, your tutorial are very valuable for me. Also, Plug can be used to run tests from your neovim (no need for another tmux split / tab)
@jonhoo
@jonhoo 4 жыл бұрын
Ah, but I prefer to run the tests in a separate and dedicated shell :)
@user-je3qh9gu7o
@user-je3qh9gu7o 9 ай бұрын
Trying to learning rust, coming from a javascript career, and trying to transition into things i find more interesting than daily redux meandering. Very helpful. I don't have a cs degree, so in trying to learn rust i am also trying to learn cs. So the deeper explanations are wildly more helpful than just pure 'x does y', without the 'because' I will never actually be able to write anything useful... so thanks!
@abdullahajibade7210
@abdullahajibade7210 6 ай бұрын
if you want to learn or you learning. there are videos for that. ppl could recommend some for you if you want. there are alsoo full tutorial as the video is centerred on smart pointer
@user-ov5nd1fb7s
@user-ov5nd1fb7s 4 жыл бұрын
Great videos. Keep em coming.
@daltontinoco7084
@daltontinoco7084 2 жыл бұрын
I love how you try and show errors in rust, but rust is like, "Nah dude, I can make it work, trust me!" Unsafe code is hard to write lolol. Good video, earned my sub for sure. Thankyou!
@sproccoli
@sproccoli 3 ай бұрын
this is the perfect way to teach/learn this stuff.
@cr74life96
@cr74life96 2 жыл бұрын
This series is just perfect :)
@TheHellst0rm
@TheHellst0rm 2 жыл бұрын
Thanks for this series! I wonder if you could make a stream for advanced Cargo/build topics? E.g. linking to native libraries, passing compiler parameters, writing Bild scripts, different targets etc. Maybe even something about linkers in general and how Rust interfaces with it (it's often discussed in C courses, but somehow a bit neglected in Rust).
@yakupc
@yakupc 3 жыл бұрын
I respect what you do. These are great videos. You should consider enabling the support button on KZbin
@jonhoo
@jonhoo 3 жыл бұрын
Unfortunately I can't for visa reasons :) But maybe one day!
@willemvanderveen7567
@willemvanderveen7567 3 жыл бұрын
Keep it up man great videos!
@Tamazakis
@Tamazakis 3 жыл бұрын
std::borrow::Cow. milk it. shake it. drink it... now you own it... no need to return it. the magic of Cow
@rurunosep
@rurunosep Жыл бұрын
I think that the PhantomData in the Rc might not be necessary. I noticed in the PhantomData chapter of the Nomicon there's a section that says that if you implement Drop on a Vec, for example, the compiler will automatically consider that Vec owns values of type T (at least for the purposes of the drop check?) even though it actually just owns a pointer. And I imagine it works exactly the same in the case of our Rc here.
@jonhoo
@jonhoo Жыл бұрын
There's currently a lot of debate around PhantomData and the drop check, so the discussion around there may be slightly dated. I recommend github.com/rust-lang/rust/pull/103413 for the latest discussion.
@jacklong2182
@jacklong2182 2 жыл бұрын
Great tutorial , thanks for share
@dionysis_
@dionysis_ Жыл бұрын
This is so useful! 🙏
@uoweme5grand
@uoweme5grand Жыл бұрын
These videos are great. Just wondering for the rest of the people watching (those of you who are coming across these concepts for the first time or maybe had only heard about it), does it also take you guys 3 to 4 times the video length to fully understand everything? It takes me a while ...
@FreemanPascal
@FreemanPascal Жыл бұрын
Jon, is your vim configuration available? I would like to setup vim to work with Rust and yours appears to work really well for that.
@Sina-xw4xp
@Sina-xw4xp 4 жыл бұрын
This was advanced stuff :)
@EgnachHelton
@EgnachHelton 3 жыл бұрын
An interesting way to think about RefCell vs Mutex: In multithreaded code, Mutex force a thread to wait for another thread leaving the critical section. RefCell does the same in single threaded code except that it knows that "the other thread" would never leave the critical section since there's only one thread, so it just terminates the program
@MauriceChavez353
@MauriceChavez353 3 жыл бұрын
Make Jon the rust mascot!
@FlaviusAspra
@FlaviusAspra 4 жыл бұрын
Question about Cell: why would we use Cell at all? Why not simply pass T or & T to a method, but Cell?
@jonhoo
@jonhoo 4 жыл бұрын
You would rarely pass a Cell to a function. But you _would_ pass a &Cell to a function. Or, more commonly, something like an Rc. Those are the cases where Cell is useful - when you have multiple shared references to the Cell, and you want to mutate through _one_ of them.
@cryptomando
@cryptomando 2 жыл бұрын
Thank you Jon
@smartislav
@smartislav 4 жыл бұрын
#34:30 the best way to reproduce these kinda bugs is to use a barrier before the actual operation that's supposed to fail
@filipbielejec9038
@filipbielejec9038 2 жыл бұрын
From ~55:00 - is there a reason you cannot implement Drop (and DropMut) for RefCell directly and going via these helper Ref/RefMut types?
@alexzander__6334
@alexzander__6334 2 жыл бұрын
great videos. thanks.
@menfie
@menfie 3 жыл бұрын
You do `escape + Shift-O` after opening bracket to get into it. I was doing same before I discovered `let delimitMate_expand_cr=1` option for delimitMate. Highly recommended.
@sharperguy
@sharperguy 3 жыл бұрын
I think I've seen you use these types in other videos. However, I don't recall you explaining in detail WHY you needed it in that case. I haven't seen many of your videos yet, though. Are there any where you are writing an example, and using these types, and explaining in detail why it's needed?
@jonhoo
@jonhoo 3 жыл бұрын
I think the standard library documentation gives pretty decent examples of when you might need each one, but the basic summary is: - You need Rc/Arc for when you want to share access to a value for an indeterminate amount of time. - You need Cell for when you want the ability to replace a shared value. - You need RefCell/Mutex for when you need to mutate a shared value.
@EgnachHelton
@EgnachHelton 3 жыл бұрын
6:24 No, Cell has the same memory layout as T thus cannot be use as recursive type storage.
@bongjunjang5683
@bongjunjang5683 3 жыл бұрын
pure gold
@naveendavisv
@naveendavisv 4 жыл бұрын
thanks for sharing.
@tsalVlog
@tsalVlog 4 жыл бұрын
the deref explanation made a WHOLE bunch things just click for me.
@FlaviusAspra
@FlaviusAspra 4 жыл бұрын
Question before we dive into Cell: why were the normal references with &, mut&, etc at the syntax level not enough? Or why was the syntax of the language not extended for this interior mutability things?
@jonhoo
@jonhoo 4 жыл бұрын
At the language level, you only have two types of references: shared (&) and exclusive (&mut). Rust lets you modify things through the latter, but not the former, because that is the only case where it's _definitely_ safe to do so. If no-one else has a reference, then there cannot be concurrent reads or writes to the value, and you cannot invalidate any other references by changing the thing pointed to. The same is not true for shared references, so Rust does not allow modification through those references. Interior mutability types (types that let you modify through a shared reference) let you do that because they maintain additional, special-case invariants that the compiler cannot check, that ensure that no conflicts arise even if you modify through a shared reference. There are _lots_ of ways to do so (CPU atomics, Mutexes, Cell, RefCell, etc.), and so it's not clear how the language would be "extended" to allow them. They all rely on implementation details of some particular algorithm.
@leolin652
@leolin652 7 ай бұрын
Thank you!!
@viewing4748
@viewing4748 2 жыл бұрын
Does rwlock guarantee any sort of fairness between readers and writers or is that something you'll have to implement yourself? Can starvation happen with rwlock? In general should I be using rwlock for threaded apps or using crates liek tokio and rayon? are there other popular crates do that kind of stuff with?
@huxleyrummy9544
@huxleyrummy9544 4 жыл бұрын
Just started watching the video. As Jon says, "it seems antithetical". From knowing some Rust, it sure seems. If so, is these Cell and RefCell types made with some special compiler support or are they just regular vanilla rust with maybe some unsafes?? Haven't watched the rest of video though neither searched theses types source code :D
@jonhoo
@jonhoo 4 жыл бұрын
Hopefully the rest of the video explained it sufficiently!
@workharderkomrade9662
@workharderkomrade9662 4 жыл бұрын
Quick question: how do you set a dark theme for the docs ?
@jonhoo
@jonhoo 4 жыл бұрын
Click the little "brush" to the left of the search field at the top :)
@farseendeveloper461
@farseendeveloper461 3 жыл бұрын
How do you move firefox's navigation bar to the bottom? Didn't it get messed up when they changed the address bar recently?
@jonhoo
@jonhoo 3 жыл бұрын
I'm continuously updating my userChrome.css over at github.com/jonhoo/configs/blob/master/gui/.mozilla/firefox/chrome/userChrome.css :)
@gangwang2547
@gangwang2547 3 жыл бұрын
really great, what's your neovim color scheme
@jonhoo
@jonhoo 3 жыл бұрын
It's called gruvbox, and I'm specifically using the "hard" dark version :)
@ilyasb4792
@ilyasb4792 3 жыл бұрын
"Great, it failed, fantastic" 36:29 It wasn't even ironic lmao
@jonhoo
@jonhoo 3 жыл бұрын
Being able to predict exactly how something won't work is a special kind of good feeling!
@dickheadhex418
@dickheadhex418 2 жыл бұрын
I think the array example for threading racing didn't work because the array values aren't written individually to the unsafe cell, instead each time set() is called the underlying unsafecell is assigned a new array so it will either be all 2s or all 1s but never interleaved. But also because of how fast the computer is, the order is maintained usually so it shows 2s everytime. is this the right way to look at it?
@joeldsouzax
@joeldsouzax 3 ай бұрын
I think the reason the Cell Test didnt show up that interleaving because you either set the entire cell value as an array of 1 or 2 ...10240 elements, this meant that that OS must have done both concurrently and the one that was did it last was shown in the print.
@SomeNullBytes
@SomeNullBytes 2 жыл бұрын
At 28:16, is it possible that it was due to short string optimization?
@karelhrkal8753
@karelhrkal8753 Жыл бұрын
1:27:49 I keep wondering: why do you need to manually drop the "inner" value? Will the constructed Box not destroy it?
@karelhrkal8753
@karelhrkal8753 Жыл бұрын
1:21:39 Oh, "inner" is a raw pointer and dropping it doesn't (really) do anything. The actual data of T is still dropped only once by the Box. Thanks.
@VivekYadav-ds8oz
@VivekYadav-ds8oz 2 жыл бұрын
28:30 I allocated 1000 50-character long Strings to force the deallocation of the referenced String, the program still doesn't crash/panic. I think that's because you don't really have a reference/pointer to the String so much as you have a pointer/reference to the _value_ member variable of the _UnsafeCell_ struct, which still remains a valid String. If the Allocator was the reason, it wouldn't have printed "world" in that example it would've still printed "hello".
@jonhoo
@jonhoo 2 жыл бұрын
It's more likely because deallocation doesn't immediately return the memory to the operating system (which would cause a segfault on access), or wipe that memory in the meantime (which would yield garbage results). Usually, deallocation just means "make available to other allocations", so if you just allocate lots of other strings with the same contents, you'd just expect to have the same memory being overwritten many times with the same value, which means you wouldn't notice when trying to read through an old pointer.
@cogidtjr
@cogidtjr 3 жыл бұрын
Thanks!!!
@sodiumsalt
@sodiumsalt 3 жыл бұрын
Might I suggest delimitMate nvim extension? You seem to be manually expanding {} a lot, and it has automatic {} expansion on carriage return.
@jonhoo
@jonhoo 3 жыл бұрын
I despise anything that automatically inserts things into my editing buffer, because my experience is that they inevitably get in the way. I'd rather type the extra character :p Generally I'm not bottlenecked by typing but by my own brain!
@juchemz
@juchemz 7 ай бұрын
Is there any reason why Cell doesn't use Clone instead of Copy for the bound on get?
@karelhrkal8753
@karelhrkal8753 Жыл бұрын
19:50 You don't even need threads to break it. Just make a reference *into* the value stored in the cell, and then replace the value. Boom, instant dangling pointer.
@RitobanRoyChowdhury
@RitobanRoyChowdhury 4 жыл бұрын
You explained Mutex as an RwLock which doesn't distinguish between readers or writers. When might be a practical situation where a Mutex is a better choice than RwLock?
@everestshadow
@everestshadow 4 жыл бұрын
From my experience RwLock only makes sense when I have way more read than write on the shared state. Otherwise I find Mutex to be more useful especially under contention. That being said it's best you measure it yourself for your use case.
@mithradates
@mithradates 2 жыл бұрын
Interesting, the code at 1:34:38 doesn't generate an error anymore!
@veetaha
@veetaha 4 жыл бұрын
@Jon Gjengset, might I ask you to increase the mic volume? It is too low to be able to listen to w/o headphones on both of my laptops...
@jonhoo
@jonhoo 4 жыл бұрын
Hmm, that doesn't sound right. Is the volume in the KZbin player set too low perhaps? The audio recording level is pretty standard for videos as far as I can tell?
@RandomUser311
@RandomUser311 4 жыл бұрын
@@jonhoo it's somewhat low but not terribly bad on my tablet as well.
@jonhoo
@jonhoo 4 жыл бұрын
Interesting. My recording level is where all the sources I've found on the topic say they should be, so not sure what's happening then.. I'll see if there's anything I can do about it for next video!
@veetaha
@veetaha 4 жыл бұрын
@@jonhoo Well even having the volume slightly higher won't hurt because lowering it is way easier than reaching the ceiling of the max volume and not being able to increase it to the satisfying level. I should say that listening with headphones is alright, but maybe it's just my fridge is overly loud in the background ;D
@jonhoo
@jonhoo 4 жыл бұрын
I actually just now found the problem! It looks like KZbin only performs loudness _reduction_, not boosting, so it requires that you perform loudness normalization before uploading if your input signal is relatively quiet (which my voice is). I sadly can't fix the _current_ video, but will make sure that future videos have an appropriate audio level!
@slmjkdbtl
@slmjkdbtl 4 жыл бұрын
Which LSP client (vim plugin) and server (rls/analyzer?) are you using?
@jonhoo
@jonhoo 4 жыл бұрын
I'm using coc in neovim with rust-analyzer
@slmjkdbtl
@slmjkdbtl 4 жыл бұрын
@@jonhoo thank you!
@Trequetrum8
@Trequetrum8 Жыл бұрын
I'm not sure why `x1.set([1; 1024])` should've failed. Are we assuming that this is being written usize bits at a time into memory and therefore the threads might clobber each other? Certainly I wouldn't expect my compiled code to have a loop here setting the indexes of my array. The compiler should already have inlined the binary representation inside the binary executable.
@BboyKeny
@BboyKeny Жыл бұрын
I was thinking that the threads we're in sync perfectly like thread 1 sets 1 at the first element then thread 2 sets 2 at that place. Then sometimes the first thread starts 1 tick later. That's why it was mostly all 2's and sometimes all 1's. But I might be completely wrong about this.
@Trequetrum8
@Trequetrum8 Жыл бұрын
@@BboyKeny Maybe we should just compile this and look at what's happening. I would be very surprised to see either thread setting anything via indices.
@busydying
@busydying 3 жыл бұрын
Hey, Jon :) I watch all of your streams in recordings, not because I can't do it in live, but because I'm such a slow learner, that I constantly pause it to think or google something. The whole experience is almost perfect, but I lacking seeing the chat, sometimes it feels as I'm missing some context with it. So if there's any possibility to add it into the video, it would be great.
@jonhoo
@jonhoo 3 жыл бұрын
There's a link to the video with chat included at the bottom of the video description :)
@busydying
@busydying 3 жыл бұрын
@@jonhoo Ah, sorry, I missed it somehow, thanks :)
@michaelritsema7108
@michaelritsema7108 4 жыл бұрын
Are you sure what you say at 29:00 is accurate? It seems like this was the exact thing Rust is trying to prevent from happening. I'm new to rust but it seems to be it works and will always work because you are still pointed to the same underlying UnsafeCell type. Perhaps because mem::replace uses the same memory address not matter what you replace it with. I'd be interested to hear your feedback on this.
@jonhoo
@jonhoo 4 жыл бұрын
Yes, absolutely, the statement there is accurate. And yes, Rust prevents those things from happening _when you're writing safe code_ . But here, we are specifically writing unsafe code, which gives us the ability to fiddle with raw pointers, and there it is up to _us_ to uphold the safety contract. What I'm trying to explain at that segment is why we need to not give out references from Cell - it's because doing so would mean that the user could write code that references invalid memory without anything in _their_ code being unsafe. I think maybe part of what has you confused is that String itself is also a pointer to some heap allocation. When we print a string, it's true that the location of the UnsafeCell hasn't changed (so the pointer to the String hasn't changed), but the pointed-to value by that String _has_ changed. This might be clearer if I'd dereferenced the pointer to get a &str directly to the string on the heap, rather than just go one level deep to get a &String. Hope that helps a little!
@pauloalmeida2126
@pauloalmeida2126 Жыл бұрын
Came here for PhatomData's explanation; First thing Jon said: "This is going to make your head hurt";....... Missing accomplished bro :)
@Agryphos
@Agryphos Жыл бұрын
I was able to get the interleaving that Jon attempted around 34:00 by using [u8; 320000] as inner values for the Cell. Most of the time the result was uniform but I got interleaving every now and then
@Knirin
@Knirin Жыл бұрын
That is a separate bug. Probably one in the compiler not Your or Jon’s code. Interleaving of the values is not the expected behavior. The expected behavior is what Jon got. An array that is unpredictably either all 1s or all 2s. The data race should be in the final assignment not in the array creation. If interleaving was the expected behavior data corruption from any two threads using the stack in a similar time window would cause a lot of havoc.
@Agryphos
@Agryphos Жыл бұрын
@@Knirin That's what I thought at first as well, that both would be creating a full array and the race is who gets to set the pointer. Interesting to know that is correct and that I had a separate bug here.
@Knirin
@Knirin Жыл бұрын
@@Agryphos Having had more time to think about it, I think the issue is in the memory allocator. It may not be thread safe for stack allocations in excess of the CPU’s L1 cache. Which are generally under 64KB. You may need to get a few times over the L1 cache size to reliably see an issue. I am kind of surprised that Rust tries sticking multi KB data structures on the stack. Every other compiled language I have dealt with is throwing things onto the heap before a data structure is a KB in size, usually transparently to the user. Transparently until your compilation target doesn’t have an operating system or you are trying to write a kernel in said language anyway. The jokes about C being an easier to read assembler aren’t far off.
@debasishraychawdhuri
@debasishraychawdhuri 3 жыл бұрын
I honestly don't understand why this video has so few likes. This is what I have been looking for from the moment I started learning rust.
@echoptic775
@echoptic775 2 жыл бұрын
Whats the terminal ur using?
@jonhoo
@jonhoo 2 жыл бұрын
I'm using fish shell in the Alacritty terminal.
@karelhrkal8753
@karelhrkal8753 Жыл бұрын
Hey, I added a comment the other day with a link to the Rust playground and I don't see it anymore. Is KZbin automatically deleting comments with links or something?
@MuhamadAzmy
@MuhamadAzmy 3 жыл бұрын
I think the problem with the "not failing" test regarding large array writing is that both threads allocate the array first on their stack. then ONLY one set call to replace the value in set. So it ends up either one of the 2 threads commits this "set" last, hence only its value shows up.
@jonhoo
@jonhoo 3 жыл бұрын
They should be racing on their call to set, and each set sets the array _by value_, which should mean a full memcpy. Arrays in Rust aren't pointers, they are values on the stack, so since Cell contains an array, it really contains all those bytes, so set has to memcpy.
@MuhamadAzmy
@MuhamadAzmy 3 жыл бұрын
@@jonhoo Oh, that makes much sense, thank you for clearing this up :)
@manojbabu3640
@manojbabu3640 Жыл бұрын
To check if you have all 2's or 1's set, may be you could print sum.
@secondengineer9814
@secondengineer9814 Жыл бұрын
So the only difference between Cell and UnsafeCell seems to be the function signatures?
@naveendavisv
@naveendavisv 4 жыл бұрын
What is exclusive reference means ? I heard about mutable reference (&mut T ) and immutable reference (&T)
@maboesanman
@maboesanman 4 жыл бұрын
Exclusive reference is a synonym for mutable reference. Edit: this is true in a practical sense, but generally you choose one or the other (mutable or exclusive) depending on what property of the reference you are trying to emphasize. This doesn’t affect the code directly, but it helps to understand design intentions when one description is chosen over the other.
@PaulSebastianM
@PaulSebastianM 4 жыл бұрын
Immutable non-exclusive reference, mutable exclusive reference. Non-exclusive meaning more than one reference can be created at a time, exclusive meaning only one reference can be created at a time.
@jonhoo
@jonhoo 4 жыл бұрын
The other comments are quite right - exclusive is a synonym (and arguably a better word for) mutable references. Take a look at docs.rs/dtolnay/0.0.7/dtolnay/macro._02__reference_types.html. You can read "mut" as "mutually exclusive" if that helps.
@benedyktjaworski9877
@benedyktjaworski9877 4 жыл бұрын
@@jonhoo I like dtolnay’s simplified convention to consistently use the terms ‘mutable reference’ (since when you have it - you can always mutate what’s behind it, and it’s in line with the keyword) but ‘shared references’ (since you always can share them and thus you need those in multithreaded contexts, and _sometimes_ also mutate through them). _Mutable (because exclusive)_ vs _shared (and possibly mutable)_ isn’t symmetric but it does click for me. I can mutate with &mut T (but that’s it, no sharing); I can share (and might be able to mutate) with &T.
@maspe1
@maspe1 3 жыл бұрын
Why doesn't Cell have trait bounds on its T? What good is a Cell around a T that doesn't implement Copy?
@jonhoo
@jonhoo 3 жыл бұрын
In general Rust prefers to have bounds on impls, not structs. There are a couple of reasons for that, but the primary one is that it avoids reduces how many times the bounds have to be repeated throughout your code base, and the code base of anyone embedding your type.
@VivekYadav-ds8oz
@VivekYadav-ds8oz 2 жыл бұрын
Also why being Send is opt-out? That seems very dangerous that it isn't opt-in. What if I'm just implementing Structs as I do everyday, and one of them isn't really safe to send to threads, and I forget to opt-out of it. Then send it over? Kinda curious why that's the case.
@jonhoo
@jonhoo 2 жыл бұрын
Send is an auto-trait, so it's not _quite_ that it's opt-out. A type is !Send if _any_ of its members are !Send. And raw pointers for example are !Send. So I think it's very unlikely for you to have a type that truly isn't thread safe that is still Send unless you use unsafe. And if you use unsafe, thread-safety is one of the (many) things you have to check. Even then though, the auto-implementation will probably make your type !Send already, just because it almost certainly includes some kind of raw pointer.
@VivekYadav-ds8oz
@VivekYadav-ds8oz 2 жыл бұрын
@@jonhoo That's good to know that in practicality my types would be !Send unless I make them Send by unsafe impl. But I'm still confused why it was decided that Send would be an auto-trait? I have to wrap all my types in Arc anyways before sending (it I want shared ownership). So it's not like trivial structs benefit from this auto-trait, as I have to wrap my type with a Send type anyways. My guess is that you can't even send a clone of your type if you wanted because you have to move the clone too. That _would_ get annoying.
@jonhoo
@jonhoo 2 жыл бұрын
I'm not sure I follow? Being able to send a type to another thread is very common, such as passing it to a closure in thread::spawn or sending it over a channel. Those cases don't require sharing, just sending, and it'd be really unfortunate if you had to wrap all of those in Arc unnecessarily.
@VivekYadav-ds8oz
@VivekYadav-ds8oz 2 жыл бұрын
I still don't get why you would use a _Cell_ type for storing the refcount. We're in a single-threaded environment (as far as Rc is concerned), so there should be no race conditions, we can just modify refcount through the pointer!
@jonhoo
@jonhoo 2 жыл бұрын
Even if a type is !Send, it's not okay to give out a mutable (i.e., exclusive) reference to a value contained inside that type. Consider a single-threaded program that walks a cyclic data structure, and ends up vising a single node twice. It then ends up with two concurrent mutable references to a single value. Now imagine it passes those to a method like mem::swap - bad things will likely happen. Worse yet, the compiler is allowed to optimize based on mutable references being exclusive, so it might apply an optimization that assumes that the second reference will not change (since we have an exclusive reference), but that optimization is invalid since the same value can change by changing through the first reference. It is undefined behavior to _ever_ have multiple mutable references to a single value concurrently, no matter whether you're in single-threaded or multi-threaded context. Hence the use of Cell, which allows mutation through a shared reference (though note it never _gives out_ a mutable reference).
@VivekYadav-ds8oz
@VivekYadav-ds8oz 2 жыл бұрын
@@jonhoo Damn, is it really undefined behaviour to do unsafe { *refcount += 1 } ? That's wild if that's true. It seems like a very simple case where we don't pass a reference (mutable or shared) to anyone. We modify it, but only through our code. Using a _Cell_ here would be more out of principle than it being actually needed, imo.
@jonhoo
@jonhoo 2 жыл бұрын
@@VivekYadav-ds8oz Mutating directly through a shared reference without UnsafeCell is *always* undefined behavior, precisely because the compiler may optimized based on the assumption that the pointer isn't shared, which would be invalid (and this produce incorrect code) if it was shared. You could do it with UnsafeCell if you *knew* it was truly not shared (even on the same thread), because that prevents some of those optimizations.
@oskarberndal5310
@oskarberndal5310 4 жыл бұрын
Hello Jon! Thanks for the great video =) I love coding along with these. It never really became clear to me why I would prefer an 'Rc' instead of a normal shared reference '&'. It seems to me (and this is probably wrong) that the benifits of '&T' and 'Rc' are similar except that the compiler sometimes can infer when all the references through '&T' are dropped and we get the 'T' back. However, we can never really depend on all the references through 'Rc' being dropped at any point in our code, so we could as well just use a '&T' which never gets dropped (if that makes sense). I would be very happy if you could elaborate why I'm wrong here but you are a busy man and I understand if you have other things to do - anyway thanks for the awesomely prepared videos cheers from sweden =)
@jonhoo
@jonhoo 4 жыл бұрын
Hi Oskar! Glad you're enjoying the videos :) So, the big difference between &T and Rc is that the former borrows a value, whereas the latter has "partial ownership" of a value. With a &T, the compiler knows, at compile time, what value the reference borrows, and how long that value is live for. And it checks that the &T never outlives the value (for example because the function holding it returns), and that it never conflicts with another type of borrow (&mut T). With Rc, there is no value "being borrowed", not really. There is an owned value that lives on the heap (think Box), where the Rcs organize among themselves so that only when the last Rc goes away is the Box dropped. By upholding that contract, every Rc can reference the T safely, since they all do so with shared references, and the target is always valid (since it's owned and on the heap).
@aqua3418
@aqua3418 2 жыл бұрын
Regarding turning things into *const and *mut. It is UB to turn a & reference into an &mut. So *const generally signifies it's an immutable reference, and *mut generally signifies it's a &mut, and it should always be kept the way. Of course, if it comes to memory passed from FFI, all bets are off and you'll need to know how the FFI is managing the memory and use proper Rust C structs that properly imitate the c ones, and blah blah blah. Advanced topic for another time. The main point is the Rust ones are easy to remember.
@mavichovizana5460
@mavichovizana5460 2 жыл бұрын
kzbin.info/www/bejne/boCTf6dvpr6SrNE after I put this `drop(inner)` and use inner afterward, the code can still compile. I'm confused since you mentioned that after the drop, `inner` can't be used.
@jonhoo
@jonhoo 2 жыл бұрын
Ah, yes, I was being overly eager there. I explain what's going on over in gist.github.com/jonhoo/7cfdfe581e5108b79c2a4e9fbde38de8#gistcomment-3805900
@mavichovizana5460
@mavichovizana5460 2 жыл бұрын
@@jonhoo Thanks a lot! Love your crust rust series! Fantastic vlogs!
Crust of Rust: Channels
1:43:12
Jon Gjengset
Рет қаралды 77 М.
Crust of Rust: Atomics and Memory Ordering
2:39:20
Jon Gjengset
Рет қаралды 79 М.
Why You Should Always Help Others ❤️
00:40
Alan Chikin Chow
Рет қаралды 42 МЛН
When Steve And His Dog Don'T Give Away To Each Other 😂️
00:21
BigSchool
Рет қаралды 11 МЛН
Hot Ball ASMR #asmr #asmrsounds #satisfying #relaxing #satisfyingvideo
00:19
Oddly Satisfying
Рет қаралды 23 МЛН
Crust of Rust: Iterators
1:26:27
Jon Gjengset
Рет қаралды 95 М.
Crust of Rust: Functions, Closures, and Their Traits
1:06:40
Jon Gjengset
Рет қаралды 84 М.
Jon Gjengset - Towards Impeccable Rust
55:59
Rust Nation UK
Рет қаралды 23 М.
Crust of Rust: Declarative Macros
1:36:11
Jon Gjengset
Рет қаралды 62 М.
Arc instead of Vec? | Prime Reacts
37:18
ThePrimeTime
Рет қаралды 61 М.
Rust's Alien Data Types 👽 Box, Rc, Arc
11:54
Code to the Moon
Рет қаралды 133 М.
Impl Trait aka Look ma’, no generics! by Jon Gjengset
1:09:05
Copenhagen Rust Community
Рет қаралды 26 М.
You MUST KNOW These Traits in Rust
18:36
Oliver Jumpertz
Рет қаралды 10 М.
Crust of Rust: Subtyping and Variance
1:39:41
Jon Gjengset
Рет қаралды 58 М.
What model of phone do you have?
0:16
Hassyl Joon
Рет қаралды 78 М.
ПРОБЛЕМА МЕХАНИЧЕСКИХ КЛАВИАТУР!🤬
0:59
Корнеич
Рет қаралды 3,7 МЛН
#miniphone
0:16
Miniphone
Рет қаралды 1,3 МЛН
ЭТОТ ЗАБЫТЫЙ ФЛАГМАН СИЛЬНО ПОДЕШЕВЕЛ! Стоит купить...
12:54
Thebox - о технике и гаджетах
Рет қаралды 153 М.