Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: remove weak pointers #1232

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions text/0000-rust-is-strong.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
- Feature Name: rust-is-strong
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary

Abandon support for Weak on Rc and Arc in favour of external crates that
provide the functionality. This resolves some open API questions, while making
them genuinely "pay for what you use".

# Motivation

Weak reference-counted pointers have long been a bit of an awkward issue for
the libs team. As a refresher, this is the current Rc/Arc design:

Creating a new `Rc<T>` allocates an `RcBox<T>` which stores all the actual
Rc-meats. The layout of these structs is as follows:

```rust
struct Rc<T> {
ptr: *const RcBox<T>,
}

struct RcBox<T> {
strong_count: Cell<usize>,
weak_count: Cell<usize>,
data: T,
}
```

There's some slightly complex logic for the two counts that I think was stolen
from boost, but for simplicity I'll assume they store the actual counts.

Rcs frob the strong_count, while Weaks frob the weak_count. There are 4
interesting cases when dropping an Rc or Weak:

* Only weak hits 0: do nothing
* Only strong hits 0: drop `data` but *don't* free the RcBox
* Strong hits 0 while weak was already 0: drop and free
* Weak hits 0 while strong was already 0: free

The primary motivation for this design is cycles: If you have a cycle of
Rcs, it will be impossible to actually free it, because each element keeps
the next element alive, ultimately keeping itself alive.

However if you break a cycle with a Weak, one of the nodes will have its
contents dropped, which will in turn cause the next node to be *completely
freed*, cascading all the way around the cycle until the weakly-pointed-to
node finally becomes properly freed.

An example:

```text
- is strong link
~ is weak link
@ is dropped
$ is dropped + freed (freeing is cash-money)

owner (on the stack, say)
|
v
A ~> B
^ |
| v
D <- C

@
|
v
A ~> B
^ |
| v
D <- C

@
|
v
A ~> @
^ |
| v
D <- C

@
|
v
$ ~> @
^ |
| v
$ <- $

@
|
v
$ ~> $
^ |
| v
$ <- $
```

Great!

Note that if `owner` had a pointer to any other node than B, part of the cycle
will be dropped before `owner`.

However weak pointers are fundamentally something that you have to pay for
*even if you're not using them*. Every RcBox will consume an extra pointer of
space on the heap, and the code needs to include checks for weak-pointer
conditions regardless.

Note that we have *never* received complaints about this, to my knowledge. Servo
and rustc happily use Rc without Weak. This isn't so much a "this is tragedy"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Servo uses arc::Weak.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Servo has both some data in Arcs that may have weak references, and some other data in Arcs that never do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the RFC it sounds like rustc and Servo don't use any weak pointers and are happy like that. I just don't want that we take decisions based on incorrect information.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it’s a bit misleading. But there are also some cases where we have unused weak counters where the extra memory usage hasn’t bothered us yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rustc uses Weak in at least one place, as a parent pointer in the rustc_resolve custom tree of Modules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So stop lieing in the RFC please!

argument so much as a "this is philosophically inconsistent". The standard
library generally favours a "pay for what you use" philosophy. There are of
course exceptions for memory-safety (extra checks). However to my knowledge
there are two major exceptions:

* Mutex poisoning
* Weak pointers

Mutex poisoning is a friendly guard against not considering *strong*
exception safety, and I don't want to get into that can of worms. I will say
it actually *was* measured to have negligable impact.

Weak pointers, on the other hand, are simply a feature that bloats users
regardless of whether they use it or not.

The other reason to be suspect of weak pointers is they raise some awkward
questions for the current unstable APIs:

```rust
fn try_unwrap(rc: Rc<T>) -> Result<T, Rc<T>>
fn downgrade(&self) -> Weak<T>
fn weak_count(this: &Rc<T>) -> usize
fn strong_count(this: &Rc<T>) -> usize
fn is_unique(rc: &Rc<T>) -> bool
fn get_mut(rc: &mut Rc<T>) -> Option<&mut T>
fn make_unique(&mut self) -> &mut T
```

In particular *what is uniqueness* when you have weak pointers?

# Detailed design

Deprecate Weak pointers in favour of an external crate and stabilize the
following methods on Rc and Arc:

```rust
fn try_unwrap(rc: Self) -> Result<T, Self>
fn get_mut(rc: &mut Self) -> Option<&mut T>
fn make_unique(rc: &mut Self) -> &mut T
```

Some notes:

We aren't stabilizing `is_unique` and `strong_count`. The former
is almost always expressible as `get_mut().is_some()` in a pinch, and the
latter has questionable value (since uniqueness is the only *really*
interesting state).

make_unique in now a static method to align with our general
policy on avoiding normal methods on smart pointers.

Arc doesn't currently have `try_unwrap` but this is considered an oversight.

# Drawbacks

Weak pointers aren't useless, and the full tools to properly implement Rc and
Arc aren't available on stable Rust. This would effectively slow down the
ability to use weak pointers. However it's possible to limit the functionality
a bit and use a few hacks to get the same basic functionality.

Everyone using Weak is on nightly already anyway.

# Alternatives

Accept the tiny overhead and stabilize Weak. This involves answering the
question of what uniqueness means in the context of Weak pointers.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative not mentioned: Provide both weakless and weakable Rc and Arc in rust. For example using default type parameters, Arc<T> = Arc<T, NoWeak> vs. Arc<T, AllowWeak>. This alternative is also pay for what you use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest also designing this parameter to be able to configure atomicity and ref-count size, as well, if at all desired in the future, to avoid type parameter bloat.


# Unresolved questions

Nah