Rust Ownership Rules
geekabyte.io
geekabyte.io
I find rust quite elegant but also quite complex. There's a scale for computer languages between a compiler that doesn't do enough checking (JavaScript, Python), and ones that do too much where you spend a lot of energy trying to satisfy the compiler. I think rust falls a bit too far to this latter side for my taste. I like the language but I feel more productive with Go even although Go isn't as elegant in my mind and has some flaws (nil, no generics).
I think rust is nicer for where you would previously use C or C++ but I don't work in that domain.
> A lot of functional languages offer this by avoiding mutable data, but that's high cost
This is true, but the inverse is that Rust's ownership model itself poses its own high cost in terms of a significant cognitive burden (and while I have no doubt that the burden decreases with time, I'm very skeptical that it ever gets to the point of a GC language). This is roughly what you're saying in the second paragraph, but I wanted to be a bit more explicit that the ownership model itself comes with a cost.
My experience in this regard: Once you stop thinking in OOP, and embrace Data Oriented Design (which really helps structure your program like Rust wants it to be to avoid lifetimes issues) and get proficient with type system and popular libraries, Rust is far more productive and effortless than mainstream GC languages. I routinely do non-trivial changes to my open source projects late in the evening, after long day of work, after I've finally put my kids to sleep, and despite sloppiness and lethargy it's very much effortless.
I don't have to debug weird stuff. Compiler just straight tells me what I did wrong and how to fix it. Languages expressiveness is first class, so I don't have think too much what's the best way to express this or that idea. Then - not only I don't have to worry about `free`, but I also get deterministic destruction: files, sockets and any other "resources" get closed/freed when I'm done with them. And then the libraries... they have beautiful, well-documented, and hard to misuse APIs.
Rust is a magnificent language for many things, but I'm manifold more productive in Go or Python, even in concurrent contexts. Notably, the ownership rules guarantee correctness, but they also prohibit all sorts of correct programs (e.g., functions that only run in a single threaded context or whose shared data doesn't actually mutate). I would say I have the hang of these rules, but I still struggle to make sense of the lifetime error messages (and I find this nearly impossible when I'm tired), contrary to your experience about the clarity of the compiler messaging. I don't mean to slight the compiler--communicating about lifetimes is fundamentally hard; however, the point is that these error messages must be dealt with in all code even though the overwhelming majority of my code isn't subject to race conditions in the first place.
I really want to like Rust (I love it in theory); however, frankly I don't see how Rust can approach a GC language with respect for productivity for anyone unless the domain prioritizes correctness and/or performance above all else.
My domain is games, and while this usually prioritizes performance that's not really what I care about (so I could be using Python for instance and it would be 'ok'). My take is this: When it comes to productivity even if writing the same code is easier in Python than Rust, working over time in a Python project (or any dynamic language really) is just a nightmare for me. No matter how many tests I write or how I structure my code when I have to add a new feature that touches large parts of the existing system I just have 0 confidence it is correct. I have to run my code through all the branches to make sure I didn't forget to, idk, add the new parameter to all function calls or something.
Rust on the hand is just reliable. I can start updating my code with a half baked idea, start adding types to things, removing parameters from functions, changing variables names and I KNOW nothing will sneak on me during runtime. This is more of a feature of strong type systems than Rust itself, but still this is what productivity means to me.
(Also, to me lifetimes were the hardest concept to grasp, but I think once you really get what they mean that's when the language clicks and you get the same productivity you would elsewhere)
In practice the most important things for general productivity are always: what is your team familiar with, what are you familiar with, and are the types of libraries that you need available. Quite often these are skewed in Go's favor.
Coming from JavaScript, my Rust code is often almost identical to my JS code. With perhaps a few extra `.clone()`s, a bit of extra error handling code (which is of course the benefit!), and type annotations.
In "burdensome" situations, you can always defer ownership checking to runtime with the Rc<RefCell<T>> and Arc<Mutex<T>> patterns. It's less performant than using pure compile-time checks, but not nearly as problematic as general GC, or immutable-only data structures.
For me, it does impose a (small) cost over a GC language. But other aspects of the language (enums, traits, type inference, etc) are so much better than most mainstream languages that it more than makes up for this. I write JavaScript quicker than I write Rust, but I write Rust quicker than I write Java.
Available in any ML derived language, with the GC productivity.
I think I prefer a GC most of the time, and when that's not true I'm fine if the language offers me an escape hatch (unsafe in Go, paired with a native allocator like jemalloc.)
I would never write a ray tracer in Go, although it could be done.
Straight C seems to be used for transparency and compiler compatibility though, which could be at odds with rust's current workflow.
I think pragmatically using rust, modern C++ or even C, any concurrency needs to be done at a generic and separate architectural level that is not part of the day to day refinements and features of a program. I don't think constantly thinking about data races, low level synchronization, or even lifetimes more complex than being returned from the creation scope is ever going to be a recipe for long term success.
This is all to say that I think rust's safety strengths are greatest in areas of a program that should actually be as small as possible and changed rarely.
I almost never use shared_ptr, since I know the lifetimes of the memory I use. I know the lifetimes because I keep almost all actual memory management within data structures made of mostly STL data structures. The custom data structures don't need destructors because vector<> will take care of it etc. I never use inheritance and never use polymorphism. The core logic then just uses these compound data structures made from STL containers and the lifetimes are frequently trivial.
Yes. Originally Rust seemed to be the answer to the three big questions in C: "How big is it", "Who owns it", and "Who locks it". Those are the cause of most of the vulnerabilities you see in CERT advisories. Rust finally had a working, efficient solution to all those problems. A huge breakthrough.
Then the type-theory people, the "metaprogramming" people, and the functional people got in there and mucked up a perfectly good imperative language. They turned writing code into either puzzle-solving or copying forms you don't really understand.
That already happened to C++. The "metaprogramming" people complicated templates to the point that "you are not supposed to understand this" is now normal for some standard templates. Now the C++ crowd is trying to emulate Rust features like move semantics without a borrow checker, which doesn't really work.
Go was a reaction to that. Go is a mediocre language, but it's good enough for most web back-end stuff, which is why Google developed it.
Clang tidy and Visual C++ lifetime profile are that borrow checker.
If they get good enough results, GCC and other commercial compilers will definitely adopt them as well.
For example, I was quite surprised that with all the security sales story, Azure Sphere SDK is C only, which basically torpedoes their sales pitch.
At least MSR seems to keep having a go at Checked C.
Neither that, memory safety, nor forcing you to deal with optionality is unique to Rust.
The big difference is that Rust manages to do these things while also having similar performance characteristics as C/C++. The trade-off is that the programmer has to explicitly deal with these issues.
I personally haven't yet seen a safety language feature of Rust that is not somehow available in other languages with similar claims. I would appreciate learning otherwise if I'm missing something.
I think the only feature that would push Rust to being at the forefront would be "const generics" [1] or true dependent types down the line.
[0] https://en.wikipedia.org/wiki/Software_transactional_memory
The long version is that you could use 'set!' with Vars. But this is very much an avoided practice. Another caveat is that Clojure is hosted (primarily JVM and JS), so any code you call via interop has the characteristics of the host target.
But idiomatic Clojure uses at least Atoms or Refs and even those are discouraged for anything else than storing state, all the algorithmic (transformation, branching and so on) code follows the FP paradigm.
A Rust analogy is the 'unsafe' escape hatch. You can use it but it would be unidiomatic, inconvenient and in most cases unnecessary.
tl;dr: no
Both using 'unsafe' and using String instead of an Enum would compile without runtime guarantees. But such code would stick out.
These markers are irrelevant in a single-threaded scenario.
I’ve googled everywhere but I can’t find a single source of information that completely explains this, possibly showing realistic source code and elaborating the way it could generate memory errors or be miscompiled in case of multiple mutable references.
The TL;DR is: iterator invalidation can happen even without threads.
Another example is if someone tried to use a Vec x inside the closure passed to x.drain.
> doesn’t Rust refuse to compile even when you use non-concurrency safe objects in a single thread?
and given that most types (Vec, HashMap, etc.) aren't "concurrency-safe" (thread-safe?), your answer
> yes
is rather perplexing.
Any language that relies on GC and avoids mutation e.g. by extrmely inefficient data structures (persistent data structures and other immutable data) obviously achieve mostly the same level of safety but at a huge cost.
Unsafe code blocks as opt-in in systems languages appeared for the first time in 1961, followed by multiple variations of it since then, far from being a novelty by now.
One might argue that “well those small parts of a rust program are as unsafe as the entire C# program” but that would be a completely nonsensical argument so I’m going to give you the benefit of the doubt that’s not what you meant.
Besides, the recent security exploits have proven that it is time to go back into multi-processes, and here Rust doesn't have anything to offer.
So one should tune a bit down the tone of labelling all the other languages as unsafe, even managed ones, industry standards like SPARK, or system research languages like ATS.
Rust is the perfect option for projects that:
1) are exposed to untrusted data
2) and, at the same time, their performance is critical
If one of those isn’t true, there are better options. If they are both true though, Rust is pretty much unique.
Rust and Haskell and standard C and C++ are all non-starters.
Do not confuse data races (a subset of race conditions that many languages solve) with race conditions (a way harder problem that there is no general solution to).
> A trivial counterexample is the protection against data races.
Before Rust, I was close to saying that thread-based parallelization was almost impossible to do safely, and was moving to only doing process-level parallelization.
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++.
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.
Can someone share a good, authoritative source that explains the variable passing semantics of Rust in terms that a C/C++/Java programmer can understand?
On one level, Rust has no defined ABI, so on some level, the answer to this question is "no way to tell."
On another level, Rust doesn't have pass by reference. Like C, everything is pass by value, and pointers are themselves values, sometimes called "pass by value by reference." So semantically, if you pass a value, a value will be passed, and if you pass a reference, a reference will be passed. You have control here.
On a third level, like C and C++, Rust may optimize this in whatever way it wants. Your function may get inlined, and there's no passing whatsoever. Large values may be allocated on the parent's stack frame and a pointer may be passed instead.
You seem to think you have less control over your allocations than in other languages, but this is not accurate, particularly contrasted to GCd languages.
How can I definitively tell which of the two things will happen? Under what conditions will it choose to make a copy instead of passing the address?
The idea is to detect large moves and do a pass by reference, much like Swift does. On my personal roadmap I have detecting these cases and lint against it, suggesting passing the argument as & or &mut as appropriate. That way the performance implications and tradeoffs are visible in the code you write/read.
In fact Java will leak memory over FFI boundaries as well. Rust is memory safe, but like many languages it is not leak safe. It is possible in Rust to tell it to leak memory, but that’s for special cases.
To state this another way, memory leaks are different from memory safety.
If you use reference counting in Rust, it will not be able to detect cycles. That said, it's not super easy to get a cycle accidentally.
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.
On the flip side I am very happy to trade that papercut for the simplicity offered by Rust’s move semantics compared to the minefield of c++.
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.
The guarantees of Pin are now even being used beyond self-referential types but also for intrusive data structures in tokio's concurrency primitives. Pin is not for everyday users, but it is one of the greatest success stories of Rust's unsafe system. That you would call it a mess to a forum of non-users is quite disspiriting.
The fact that Pin is expected to enable more than it does is part of the problem. It's not for usual self-referential structs. It's not for usual umovable types. It has 2000 words of documentation, and I'm still not sure what to do with it. I need to understand pin/unpin, structural/non-structural projections, and drop guarantees. It has a lot of "you can do this, but then you can't do that" rules that the compiler can't help with.
For me most Rust features are either easy to understand (like UnsafeCell or MaybeUninit), or I can rely on the compiler make me use them correctly (like borrowing or generics). But with Pin my impression is that it pushes the limits of what can be done without compiler's safety net.
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.
> A Pin<P> ensures that the pointee of any pointer type P has a stable location in memory, meaning it cannot be moved elsewhere and its memory cannot be deallocated until it gets dropped. We say that the pointee is "pinned".
That is actually the definition of "move" that I had meant, after all that is what is bad for an object that points into its own footprint. But I realise that "move" in Rust normally means the first one (but note that even Rust's own documentation uses "move" the other way in that snippet above!).
Variables don't have a "lifetime," references do. When we talk about lifetimes we talk about the lifetime of references to variables, during which time the variable cannot be moved, dropped, etc. The "lifetime of the variable must be greater than the lifetime of the reference" is a common mental model but this lifetime of the variable doesn't really come into play in reality.
Your 1) and 2) always coincide in the abstract model, though the compiler may optimize out memcpys that have no impact. When you move a pointer around, you don't move the object it points to.
As in the sibling comment, "scope" could be used, but indeed maybe we should have just called lifetimes "scopes" or something (though they are not lexical, whereas scopes are usually thought of as lexical pyramids).
I probably would just say lifetime usually, but I would be being imprecise and potentially unclear!
There's an unofficial 'transfer' crate that does provide this behavior if needed, fwiw.
When would you have pointers to a classes own memory instead of a named class member for the same data?
Edit: Another example: Imagine you have a matrix class that contains a bit of memory as a char[] member to avoid heap allocation for small matrices. Then you maybe you want your data pointer to point directly at that data (rather than looking at a separate flag, which might cause an extra branch or just be more code complexity). I'm not saying it's a good idea, just it's obvious that it might come up.
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.
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!
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
(Not trying to be pedantic, just to clarify things for less knowledgeable readers).
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.
#[lang = "unsafe_cell"]
This tells you that UnsafeCell is a Language Item[1], which basically means that the compiler does have knowledge of it.[1]: https://doc.rust-lang.org/beta/unstable-book/language-featur...
This was from the methods chapter but I think it applies globally. You either borrow to read, borrow to mutate, or consume (and possibly return a different value back)
I'm sure there will be more nuance but I think this is a good conceptual foundation to begin with.
Often the purpose is to consume the value and return a new kind of value. Maybe `validate_square` takes in `Shape` and returns `Option<Shape>`
If valid or invalid, we have consumed the shape so it cant be reused by accident and can't be used without handling the optional branches.
I'm new so maybe I'm off on this.
The analogy may not be perfect but the point is complete control of the object has been transferred to the function and so you no longer have access to it and it will be destroyed once the function ends, unless it's returned.
fn borrow_car(car: &Car); fn modify_car(car: &mut Car); fn sell_car(car: Car);
Obviously there'd be other parameters in the functions, but yeah that's the gist of it.
Rust's terminology is kind of reversed: It is stated that a mutable reference cannot be shared, while in fact a reference is called "mutable" when it cannot be shared.
The terminology is understandable and great to get started, but once you dive in deeper into atomics and internal mutability, it's rather crucial to understand this reversal.
https://channel9.msdn.com/posts/C-and-Beyond-2012-Herb-Sutte...
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.
For the multi threaded context it's amazing. Compile-time data-race checking is such a unique and useful feature. That and the great ergonomics of the locking primitives makes multi-threaded programming in rust really enjoyable.
I think this is a really underrated benefit of the ownership model. You cannot forget to acquire a lock, and you cannot keep a reference to a lock's contents past the point where you've released it.
Oh, it has for me. In fact, unexpected mutation has been one of the most difficult bugs I've encountered.
I had a method, foo, which when given a NavigableMap (java), it would throw an exception. However, if you called the same method with the same Map, no exception would be thrown (because the map was mutated deep down).
Turns out, in the 84th circle of hell after a sub-sub-sub map view was created. A cute little said something like 'if key doesn't exist, associate key with 0'.
Because NavigableMap views are mutable in java this eventually made it's way all the way back to the parent map which, consequentially, caused the map to go down a different path when the same method was re-invoked.
It took a very long time to ultimately find that mutation due to the complexity of the code.
That's the beauty of Rust: it assumes single-threaded programs don't exist.
All types — including all 3rd party libraries — are forced to be either thread-safe or explicitly not compile if they'd cross thread boundaries. Nobody has "but I don't support multi threaded programs" excuse, so you may as well throw multithreading at everything.
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.Could this not be covered by the lifetime guarantee without imposing read/write exclusivity on all values even where lifetimes are unaffected?
It's a good example, but I'm still not enirely convinced that the implication is necessarily correct.
I wonder if a sufficiently smart compiler could not detect this particular special case and relax immutability/exclusivity in other cases where no references to the internal structure of a value exist, or if it could even keep whatever r is referencing alive to uphold the lifetime guarantee.
I realise the latter could have some undesirable side-effects wrt memory usage and it raises ownership questions if there are multiple such references. It's essentially what persistent data structures in functional languages do and it may not be a good fit for Rust.
The reason why I'm even interested in this is that Rust can sometimes feel restrictive in situations where having both mutable and immutable references or more than one mutable reference to the same thing is reasonably safe, such as in the local scope. Obviously people are working around mutability restrictions by using indexes/handles instead of references, but that doesn't seem any safer.
1. In order a sufficiently smart compiler to understand if this is okay, it would have to know how the Vec<T> type actually works. But Vec<T> is a library type, and uses unsafe code, which is kind of definition-ally code that the compiler cannot understand. So, even for this specific case, it is too hard to do so today. A human cannot even tell if this is okay or not, right now, because you'd need to see the code that's in the `...`.
2. Even if it could, it's unclear if it's a good idea. The more complex analysis a compiler can do, the harder it is to explain what it's doing to end-users. Imagine that, for example, we wrote this code, with the `...` being a case where it's safe, but then we modified it to a case where `...` was not safe anymore. What would that diagnostic look like? How could it be communicated to people in a useful way?
3. There's a tension here. If you make it work for very specific reasons, any small changes would likely cause it to break, whereas it breaking up front may lead you toward a design that is overall more robust.
> It's essentially what persistent data structures in functional languages do and it may not be a good fit for Rust.
We have those! https://crates.io/crates/im
> Rust can sometimes feel restrictive in situations where having both mutable and immutable references or more than one mutable reference to the same thing is reasonably safe, such as in the local scope.
It depends! You're not wrong, but also see my comment over here: https://news.ycombinator.com/item?id=22466587 even local scope may not be enough.
Do any languages somehow use their GC to update references when a growable array is reallocated? I'm not aware of any language that does.
Furthermore, its hard to see how it could. In the case of a simple copying/compacting collector the whole world is stopped and the pointer graph traced. But you obviously can't do that every time a growable array needs to be reallocated. I understand that there are more complex solutions that avoid that pause, but still not any I'm aware of that would be exploitable for reallocating an array.
Please don’t spread FUD. The majority of commercial C++ software out there is rock solid.
Vulnerabilities are the actual problem in today's Internet connected world and why Rust has grown so fast.
As for GCs: they are not good enough for some fields, so they cannot be used. Even state of the art ones like Go's.
Go's GC is a variation of a regular mark and sweep GC: the GC starts exploring the heap to find all reachable memory, and then clears the unreachable one, meanwhile compacting GCs move the reachable memory from one place (the “from space”) to another (the “to space”), which become the new heap. Theses two garbage collectors famillies are completely different.
BTW, calling go's GC “state of the art” is debatable at best. See : https://blog.plan99.net/modern-garbage-collection-911ef4f8bd...
Go as a language was designed like that, and special attention was made to allow as much value types as possible, to allocate as much things as you can on the stack, thus reducing GC pressure. For the first five years of Go or something, Go's GC was actually pretty bad (it was probably the most basic GC you could find in any somewhat popular language), but it wasn't too much of a deal because you can avoid it most of the time when it goes in your way (much more easily than in Java for instance).
After some time, they decided to enhance it, but they were on a budget (no lots of money spent on it” actually), so because in go you can avoid allocating memory on the heap, they decided to focus on GC latency instead of throughput (if the GC's throughput isn't good enough for you, you better reduce your allocations).
Overall go is a pretty fast language, and it's an engineering success, but it's in spite of its GC and thanks to other parts of the language's design, not because Go's GC is exceptionally good (it's not, and if you read my link you'd understand how).
Since I don’t have a sense on the timescales, I will take your word for it that it was the reverse.
2. Windows was (is?) not coded in modern C++ but a bastard C or very old C++ from what I understand. Bugs were also mainly by third-party drivers and software. The industry for home user software had very bad quality, that’s is true, but that had nothing to do with C++. C, Fortran, Delphi and others were used too.
3. Videogames are in an industry where deadlines are more important than bugs and companies really don’t care about fixing stuff before release.
https://msrc-blog.microsoft.com/2019/07/18/we-need-a-safer-s...
https://security.googleblog.com/2019/08/adopting-arm-memory-...
https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Pr...
https://developer.apple.com/swift/#safety
Modern real time GC are good enough for military use, https://www.ptc.com/en/products/developer-tools/perc
For instance, you can have a library that works perfectly for any non-malicious/valid PNG image, yet have vulnerabilities for invalid, crafted files.
In fact, this is almost always the case for any product. Only really bad quality software has issues with the happy expected path. But many more will have vulnerabilities in the unexpected paths.
It has been my experience, that even the well known softwares written in C (not sure about C++) are indeed kind of crashy. Example: Open Broadcast Studio, used by many people to stream on various platforms. It is possible there to go into the settings and change them so that OBS crashes and cannot be restarted again, unless you guess, that the configuration is faulty and OBS is unable to deal with a faulty config. You can only get it to run again by removing the config or reinstalling. Great quality. VLC is another example of such crashy software. Not only did it crash many times in the past, but also cannot properly deal with unplayable files in a looping playlist and will simply bring all your cores to 100% usage.
So far some anecdotes from the well known C software world. I am not sure, whether the people in the respective community consider these softwares to be good quality.
But in many cases I'm using APIs designed by others. Most older libraries come with their own idiosyncratic memory management patterns.
Even the standard library has lots of different ways of passing reference-like things around. E.g.
https://en.cppreference.com/w/cpp/string/basic_string_view/b...
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
The code for a vector of i32s could look something like:
impl Vec {
pub fn push(&mut self, x:i32){
// internal details
}
}The "// mutable borrow occurred" was a typo. Fixed now
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.
> 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#m...
1. Ownership-based memory management is statically elided reference counting.
2. Borrowing (shared and mutable) is statically elided reader-writer locks.
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.
From what I heard about rust's learning curve, I expected complex rules and corner cases.