15 ms·
Rust Ownership Rules
- animalnewbie 7y agoSomewhat unrelated but does somebody have the 30 minutes guide to reading rust from 5 days back?
- littlestymaar 7y agohttps://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/ https://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/
- animalnewbie 7y agoThanks!
- ObscureScience 7y agoSomeone beat me to posting it. Have you tried the search though? Works pretty well, I usually sort by date.
- protomolecule 7y agohttps://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=rust&sort=byDate&type=story https://hn.algolia.com/?dateRange=all&page=0&prefix=false&qu...
- _nalply 7y agoI read somewhere that immutable and mutable borrows are perhaps alternatively understood as exclusive and shared borrows. This means, if you have a &mut then you have an exclusive borrow. No other borrows are possible as long as your &mut lives (i. e. did not yet go out of scope). Usually this exclusive borrow is used for mutating values. This dichotomy in terminology is shown when you learn about interior mutability. Here you can mutate values without having an exclusive borrow, an example is RefCell. The price you pay for this convenience is run-time checking of access. RefCell disallows mutation if some other location in your code already has asked for mutable access.
- cube2222 7y agoI suppose you can think about it as having a shared borrow to the ref cell, but using it to gain an exclusive borrow to the data underneath, to fit the intuition.
- danieldk 7y agoI read somewhere that immutable and mutable borrows are perhaps alternatively understood as exclusive and shared borrows. Yes, when it comes to exterior mutability, it's basically single mutable xor multiple immutable. The price you pay for this convenience is run-time checking of access. The nice thing is that RefCell is not magic, it's Rust all the way down. E.g. the status of the borrow is updated by the destructors (Drop) of the reference types. All administration is done using a signed integer to do reference counting. The value 0 means 'no borrows', any positive number indicates the number of immutable borrows, -1 means one mutable borrow. It's well worth reading the implementation of RefCell some time!
- jrvidal 7y ago> The nice thing is that RefCell is not magic I'd like to point out that `RefCell` does contain a bit of magic, since it is based on `UnsafeCell`, which is _the_ core "primitive" of Rust that enables interior mutability: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html (Not trying to be pedantic, just to clarify things for less knowledgeable readers).
- simias 7y agoI assume the parent meant that there was no compiler magic at play, as far as I know UnsafeCell is written in pure Rust, just using unsafe code: https://doc.rust-lang.org/src/core/cell.rs.html#1486-1488 https://doc.rust-lang.org/src/core/cell.rs.html#1486-1488 So basically if Rust's stdlib didn't provide it, you could reimplement it yourself from scratch. EDIT: actually, reading the comments, it looks like I'm wrong about that: > If you have a reference `&SomeStruct`, then normally in Rust all fields of `SomeStruct` are immutable. The compiler makes optimizations based on the knowledge that `&T` is not mutably aliased or mutated, and that `&mut T` is unique. `UnsafeCell<T>` is the only core language feature to work around the restriction that `&T` may not be mutated.
- supermatt 7y agoI am just getting into rust myself - following from that 30 min article a few days ago. From my understanding, the comments in a few of the examples are misleading. Author is stating things like "// mutable borrow occured" where there is no mutable borrow occuring. My understanding is that there is an implicit mutable borrow where the methods are being called (e.g. `original_owner.push('.');`) which is raising the errors Can anyone with more experience confirm/deny that this is the case, as I want to be sure I am not misunderstanding this
- dade 7y agoAuthor here. The "// mutable borrow occurred" was a typo. Fixed now
- supermatt 7y agoThanks. Just a quick look through and it makes more sense now. Theres also a few places where you comment saying a borrow occured before the borrow occurs (in others the comment appears afterwards, as I would expect) - i.e. the comments are in the wrong place / inconsistently placed. Obviously it doesnt affect the output of the code, but as a tutorial its probably better to correct that. EDIT: further nitpicks, i would advise against calling the borrower the "borrowing owner", and other such terms - owner has a very specific meaning when it comes to memory management, etc.
- dade 7y agoThanks for the feedback. I just quickly went through the code examples and tried to sanitise them as much as I can now. As soon as I have more free time on my hands, I will revisit and update the naming based on your feedback. Thanks!
- supermatt 7y agoNo problem - thank you for sharing your progress.
- 7y ago
- fauigerzigerk 7y agoWhat I like most about Rust is that I can pass references around without worrying whether the value they reference will continue to exist. This has been a constant mental burden for me in C++ and has led to some unnecessary defensive copying. I'm not so sure whether Rust's strong immutability/exclusivity guarantees are worth the trouble though. Unexpected mutation hasn't been a major source of bugs or mental burden for me in other languages, at least not in a single threaded context.
- supermatt 7y agoAren't the strong immutability/exclusivity guarantees specifically the reason you can pass around references without worrying if the referenced value will continue to exist?
- fauigerzigerk 7y agoI don't see a reason why that would be the case, but I remain open to be educated ...
- dbaupp 7y agoWithout the exclusivity, it would be easy for a reference to be invalidated; the classic example is let nut v: Vec<T> = ...; let r: &T = &v[0]; v.push(...); // A foo(r); // B The reference passed at B might be invalidated by A, if the vector is reallocated. It’s the shared vs exclusive borrowing that defends against this, by flagging the above program as an error.
- LessDmesg 7y agoAnd this is another point in favor of compacting GCs: non-GC languages are moving things around almost as much (i.e. growable arrays are ubiquitous, plus hashmaps may be based on them too etc). Without a GC the simple task of moving references becomes dangerous and requires the pain demonstrated to us by Rust (or leads to pervasively crashy software culture demonstrated to us by C++). With GC, this type of thing is not something the developer needs to care about.
- ash 7y agoLobster language offers interesting lightweight alternative to strict ownership model of Rust: http://aardappel.github.io/lobster/memory_management.html http://aardappel.github.io/lobster/memory_management.html
- vlovich123 7y agoThat solves ownership in terms of making sure the memory isn't deleted while something is owning it but, at least from my quick read, doesn't solve ownership issues related to concurrent access that Rust's ownership model does solve. Is that reading correct?
- ash 7y agoYou are right. But it's not needed in Lobster, because threading model is different. It doesn't have concurrent access problems: > Lobster built-in multi-threading functionality ... is different from multi-threading in most other languages, in that it does not allow threads to share memory or any other VM state. http://aardappel.github.io/lobster/language_reference.html#multi-threading http://aardappel.github.io/lobster/language_reference.html#m...
- rob74 7y agoI would argue that the phrase "One of Rust's main differentiator[s] is that it provides memory safety" should really be "One of Rust's main differentiators is that it provides memory safety without using a garbage collector". Which is the actual reason for needing (amongst others) ownership rules...
- _-___________-_ 7y agoYeah, I guess they mean differentiators against similar languages (mostly C/C++). There are few languages with "no runtime" that provide memory safety.
- jkoudys 7y agoAs someone who hasn't done much c++ in over a decade, but who loves rust, I often find the extreme focus on comparing to c++ to be a bit jarring and unhelpful. As someone who's worked more with JS/ts/java, I find the borrowing system's guarantees far more interesting wrt concurrency. Yet that's often little more than a footnote in long articles about how rust is safer than c++.
- jfkebwjsbx 7y agoWhich guarantees? Rust guarantees there are no data races (like other higher level languages). It does not guarantee anything regarding general race conditions (no language can, at least in a non-trivial way). Rust gets compared with C++ because that is the target audience. If you don’t need the properties of a systems programming language, you are better off avoiding both C++ and Rust for increased productivity.
- nicoburns 7y agoIt prevents you from sharing non-threadsafe structures between threads. A language like Java provides a concurrent hashmap, but nothing stops you from (incorrectly) using the standard hashmap. Rust will fail to compile if you share a std::HashMap across threads without wrapping it in a mutex or similar synchronisation primitive.
- quietbritishjim 7y agoI am a long time C++ user and have looked into Rust a bit but not used it in anger. One thing not mentioned in the section about ownership move: unlike C++ where you can write arbitrary code in the move constructor, in Rust the footprint of the object is copied bitwise and there is no scope to customise this at all. If you have a class where this doesn't work (e.g. because it contains pointers into its own footprint) then you either need to refactor your class for Rust (e.g. change those pointers to offsets) or disable the move trait entirely and provide a utility function for move-like creation. This is a trade off: as a class writer you get less flexibility, but as a class user you get much more predictable behaviour. And it's only possible because of the way that Rust just forgets about the original object (i.e. won't call its destructor at the point it would have done otherwise) at the language level. If it didn't, as in C++, you need some way to stop the original object freeing resources needed by the new object. IMHO, move semantics and rvalue references in C++ are amongst the most confusing and worst designed parts of the language, so this is one of the most important benefits of Rust even before you get to the reference lifetime stuff.
- oconnor663 7y ago> disable the move trait entirely and provide a utility function for move-like creation. There's no way to disable moving in general. But there is the Pin trait, which prevents you from moving certain types in certain cases, mostly to do with async/await.
- pornel 7y agoThe Pin type is a mess IMHO. It's the most C++-like corner of Rust. Its guarantees are shallow, so non-trivial uses are still unsafe. It ended up having complex interactions with other language features, creating a soundness hole. I'd rather bury it as a necessary evil that was required to ship async/await syntax, and nothing more.
- oconnor663 7y agoIt's certainly tricky to think about, when I start reading all the docs. (Someone just recently explained what "Pin projection" is to me, and I realized I hadn't really understood Pin before that.) But at the same time, it seems to mostly accomplish its goal of "you don't have to think about this in safe code." Is there a better way we could've solved the same problems? I kind of feel the same way about Send and Sync. Like, why are there two thread safety traits? What could it possibly mean to be Sync but not Send? But at the end of the day, that's what's necessary to model the problem, and I've gotten used to it.
- frankmcsherry 7y agoIn the event that it helps anyone, the following is how I think of Rust ownership stuff: 1. Ownership-based memory management is statically elided reference counting. 2. Borrowing (shared and mutable) is statically elided reader-writer locks.
- deleted 7y ago[deleted]
- oconnor663 7y ago> Ownership-based memory management is statically elided reference counting. One difference I'd want to highlight here, which sometimes trips people up: Taking a reference to a Rust object has no effect on where that object's destructor runs. It can make the program fail to compile, if the reference lasts too long. But if the program keeps compiling, then the object's destructor is always running at the same place it was before.
- clktmr 7y agoSome commenters mention Rust has compile-time data-race checking. Is this correct? From what I did understand it will only enforce a mutex to be locked before you can access specific data. However if there is no mutex, data-races won't be detected at compile time?
- steveklabnik 7y agoIt is correct. Rust prevents all data races at compile time, unless you write incorrect unsafe code. This is why people try to write as little unsafe as possible. This has nothing to do with Mutexes; mutexes are provided by the standard library and the language knows nothing specific about them.
- clktmr 7y agoI understand. It is actually kind of easy to comprehend by remembering the borrow checker will only allow one single mutable reference at a time.
- yangl1996 7y agoRust compiler forbids multiple threads to access the same memory location not protected by a Mutex.
- hexaga 7y agoThat is, only when said memory location is accessed mutably. Immutable references are perfectly ok to follow concurrently.
- rk06 7y agoThey look pretty simple and intuitive. From what I heard about rust's learning curve, I expected complex rules and corner cases.
- deleted 7y ago[deleted]