
Rust Ownership Rules - dade
https://www.geekabyte.io/2020/02/rust-ownership-rules.html
======
rob74
I 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...

~~~
pdpi
Rust doesn't provide memory safety in the GC sense — it can and will leak
memory in ways Java wouldn't. Rather, it provides safety from all data races,
which is something that conventional GCs don't give you.

~~~
mqus
How does java prevent memory leaks which rust couldn't ? Do you have examples
for that? I was not aware that Java does it and am very interested in that.

~~~
steveklabnik
One of the advantages of a tracing garbage collector is that it can recognize
cycles. This means that if you have a few objects which point to each other,
but the overall object graph is dead, a tracing GC should be able to collect
these objects.

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.

~~~
mqus
Thank you. In hindsight, this was pretty obvious and I knew it already but I
somehow didn't connect the pieces.

------
quietbritishjim
I 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
> 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
The 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.

~~~
withoutboats
I don't want to be defensive but this is completely off base, and very
disheartening to see you post. Allowing users to write safe self-referential
code was never a design goal of Pin - only to make it safe to manipulate self-
referential objects generated by the compiler, which it has succeeded at. The
incredibly overblown soundness hole had far more to do with the quirks of
#[fundamental] than Pin (and the soundness hole is being resolved without
changing the Pin APIs at all or breaking any user code).

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.

~~~
pornel
Sorry, I should have phrased that in a kinder way.

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.

------
_nalply
I 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.

~~~
danieldk
_I 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
> 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
I 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.

~~~
jrvidal
I'm not sure that's correct. `UnsafeCell` is a Rust lang item that might
disable some optimizations, I think?

[https://doc.rust-lang.org/src/core/cell.rs.html#1483](https://doc.rust-
lang.org/src/core/cell.rs.html#1483)

~~~
simias
You're right, I just saw that. Well I least I learned something new about Rust
today!

------
fauigerzigerk
What 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
Aren'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
I don't see a reason why that would be the case, but I remain open to be
educated ...

~~~
dbaupp
Without 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
And 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.

~~~
jfkebwjsbx
> pervasively crashy software culture demonstrated to us by C++

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.

~~~
littlestymaar
The op is talking about compacting GCs and Go's isn't a compacting one.

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...](https://blog.plan99.net/modern-garbage-
collection-911ef4f8bd8e?gi=15496e3661e1)

~~~
jfkebwjsbx
And I was talking about GCs overall. The point was that Go's GC is pretty much
the best GC out there used in major projects, designed to try to compete with
systems programming languages and come as closely as possible to it. A lot of
money has been put to make it as fast and deterministic as possible. It does
not mean it is the latest in research, of course.

~~~
littlestymaar
This is a real misunderstanding. Go's GC is not “pretty much the best GC out
there” by most GC standards (it's already quite good and keeps getting better,
though). And it hasn't been “designed to try to compete with systems
programming languages”.

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).

~~~
jfkebwjsbx
Thanks for the answer. I guess I have been misled, since Go proponents
(including in HN) always argue the latest iteration sof their GC (which was
discussed a few times here) had one of the lowest latencies of most production
languages; and that is what made it suitable for many tasks.

Since I don’t have a sense on the timescales, I will take your word for it
that it was the reverse.

------
supermatt
I 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

~~~
thethirdone
You are correct. The type signature for push takes a mutable reference to
self.

The code for a vector of i32s could look something like:

    
    
        impl Vec {
          pub fn push(&mut self, x:i32){
            // internal details
          }
        }

~~~
supermatt
Many thanks. I think most of the confusion is to do with the comments, some of
which the author has resolved so now my comment doesn't make much sense. But
in the context of the original statement, thanks for your confirmation :)

------
ash
Lobster 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
That 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
You 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#m...](http://aardappel.github.io/lobster/language_reference.html#multi-
threading)

------
frankmcsherry
In 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.

~~~
oconnor663
> 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.

------
animalnewbie
Somewhat unrelated but does somebody have the 30 minutes guide to reading rust
from 5 days back?

~~~
littlestymaar
[https://fasterthanli.me/blog/2020/a-half-hour-to-learn-
rust/](https://fasterthanli.me/blog/2020/a-half-hour-to-learn-rust/)

~~~
animalnewbie
Thanks!

------
rk06
They look pretty simple and intuitive.

From what I heard about rust's learning curve, I expected complex rules and
corner cases.

------
clktmr
Some 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
It 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
I understand. It is actually kind of easy to comprehend by remembering the
borrow checker will only allow one single mutable reference at a time.

