Skip to content

Commit 9da6fbf

Browse files
ThePuzzlemakerGankra
authored andcommitted
Simple Arc implementation (without Weak refs)
This is a squash of the following commits: - Fix code, remove WIP message as that was while writing this, and link to stable @ fixed 1.49 rather than latest nightly - Improve wording on deref and ignore some code blocks - Improve wording and formatting a bit cause I'm insane - Fix links - Fix links again because we all love relative links - Remove unnecessary Drop import - Use Box::from_raw instead of ptr::drop_in_place as that actually dealloc's the Box (i'm dumb and misinterpreted the std code :/); fix some desync between code in between sections - Fix tests
1 parent a858499 commit 9da6fbf

9 files changed

+441
-1
lines changed

src/SUMMARY.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
* [Handling Zero-Sized Types](vec-zsts.md)
5656
* [Final Code](vec-final.md)
5757
* [Implementing Arc and Mutex](arc-and-mutex.md)
58+
* [Arc](arc.md)
59+
* [Layout](arc-layout.md)
60+
* [Base Code](arc-base.md)
61+
* [Cloning](arc-clone.md)
62+
* [Dropping](arc-drop.md)
63+
* [Deref](arc-deref.md)
64+
* [Final Code](arc-final.md)
5865
* [FFI](ffi.md)
5966
* [Beneath `std`](beneath-std.md)
6067
* [#[panic_handler]](panic-handler.md)

src/arc-and-mutex.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ Knowing the theory is all fine and good, but the *best* way to understand
44
something is to use it. To better understand atomics and interior mutability,
55
we'll be implementing versions of the standard library's Arc and Mutex types.
66

7-
TODO: ALL OF THIS OMG
7+
TODO: Mutex

src/arc-base.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Base Code
2+
3+
Now that we've decided the layout for our implementation of `Arc`, let's create
4+
some basic code.
5+
6+
## Constructing the Arc
7+
8+
We'll first need a way to construct an `Arc<T>`.
9+
10+
This is pretty simple, as we just need to box the `ArcInner<T>` and get a
11+
`NonNull<T>` pointer to it.
12+
13+
We start the reference counter at 1, as that first reference is the current
14+
pointer. As the `Arc` is cloned or dropped, it is updated. It is okay to call
15+
`unwrap()` on the `Option` returned by `NonNull` as `Box::into_raw` guarantees
16+
that the pointer returned is not null.
17+
18+
```rust,ignore
19+
impl<T> Arc<T> {
20+
pub fn new(data: T) -> Arc<T> {
21+
// We start the reference count at 1, as that first reference is the
22+
// current pointer.
23+
let boxed = Box::new(ArcInner {
24+
rc: AtomicUsize::new(1),
25+
data,
26+
});
27+
Arc {
28+
// It is okay to call `.unwrap()` here as we get a pointer from
29+
// `Box::into_raw` which is guaranteed to not be null.
30+
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
31+
_marker: PhantomData,
32+
}
33+
}
34+
}
35+
```
36+
37+
## Send and Sync
38+
39+
Since we're building a concurrency primitive, we'll need to be able to send it
40+
across threads. Thus, we can implement the `Send` and `Sync` marker traits. For
41+
more information on these, see [the section on `Send` and
42+
`Sync`](send-and-sync.md).
43+
44+
This is okay because:
45+
* You can only get a mutable reference to the value inside an `Arc` if and only
46+
if it is the only `Arc` referencing that data
47+
* We use atomic counters for reference counting
48+
49+
```rust,ignore
50+
unsafe impl<T: Sync + Send> Send for Arc<T> {}
51+
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
52+
```
53+
54+
We need to have the bound `T: Sync + Send` because if we did not provide those
55+
bounds, it would be possible to share values that are thread-unsafe across a
56+
thread boundary via an `Arc`, which could possibly cause data races or
57+
unsoundness.
58+
59+
## Getting the `ArcInner`
60+
61+
We'll now want to make a private helper function, `inner()`, which just returns
62+
the dereferenced `NonNull` pointer.
63+
64+
To dereference the `NonNull<T>` pointer into a `&T`, we can call
65+
`NonNull::as_ref`. This is unsafe, unlike the typical `as_ref` function, so we
66+
must call it like this:
67+
```rust,ignore
68+
// inside the impl<T> Arc<T> block from before:
69+
fn inner(&self) -> &ArcInner<T> {
70+
unsafe { self.ptr.as_ref() }
71+
}
72+
```
73+
74+
This unsafety is okay because while this `Arc` is alive, we're guaranteed that
75+
the inner pointer is valid.
76+
77+
Here's all the code from this section:
78+
```rust,ignore
79+
impl<T> Arc<T> {
80+
pub fn new(data: T) -> Arc<T> {
81+
// We start the reference count at 1, as that first reference is the
82+
// current pointer.
83+
let boxed = Box::new(ArcInner {
84+
rc: AtomicUsize::new(1),
85+
data,
86+
});
87+
Arc {
88+
// It is okay to call `.unwrap()` here as we get a pointer from
89+
// `Box::into_raw` which is guaranteed to not be null.
90+
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
91+
_marker: PhantomData,
92+
}
93+
}
94+
95+
fn inner(&self) -> &ArcInner<T> {
96+
// This unsafety is okay because while this Arc is alive, we're
97+
// guaranteed that the inner pointer is valid.
98+
unsafe { self.ptr.as_ref() }
99+
}
100+
}
101+
102+
unsafe impl<T: Sync + Send> Send for Arc<T> {}
103+
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
104+
```

src/arc-clone.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Cloning
2+
3+
Now that we've got some basic code set up, we'll need a way to clone the `Arc`.
4+
5+
Basically, we need to:
6+
1. Get the `ArcInner` value of the `Arc`
7+
2. Increment the atomic reference count
8+
3. Construct a new instance of the `Arc` from the inner pointer
9+
10+
Next, we can update the atomic reference count as follows:
11+
```rust,ignore
12+
self.inner().rc.fetch_add(1, Ordering::Relaxed);
13+
```
14+
15+
As described in [the standard library's implementation of `Arc` cloning][2]:
16+
> Using a relaxed ordering is alright here, as knowledge of the original
17+
> reference prevents other threads from erroneously deleting the object.
18+
>
19+
> As explained in the [Boost documentation][1]:
20+
> > Increasing the reference counter can always be done with
21+
> > memory_order_relaxed: New references to an object can only be formed from an
22+
> > existing reference, and passing an existing reference from one thread to
23+
> > another must already provide any required synchronization.
24+
>
25+
> [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html
26+
[2]: https://github.com/rust-lang/rust/blob/e1884a8e3c3e813aada8254edfa120e85bf5ffca/library/alloc/src/sync.rs#L1171-L1181
27+
28+
We'll need to add another import to use `Ordering`:
29+
```rust,ignore
30+
use std::sync::atomic::Ordering;
31+
```
32+
33+
It is possible that in some contrived programs (e.g. using `mem::forget`) that
34+
the reference count could overflow, but it's unreasonable that would happen in
35+
any reasonable program.
36+
37+
Then, we need to return a new instance of the `Arc`:
38+
```rust,ignore
39+
Self {
40+
ptr: self.ptr,
41+
_marker: PhantomData
42+
}
43+
```
44+
45+
Now, let's wrap this all up inside the `Clone` implementation:
46+
```rust,ignore
47+
use std::sync::atomic::Ordering;
48+
49+
impl<T> Clone for Arc<T> {
50+
fn clone(&self) -> Arc<T> {
51+
// Using a relaxed ordering is alright here as knowledge of the original
52+
// reference prevents other threads from wrongly deleting the object.
53+
self.inner().rc.fetch_add(1, Ordering::Relaxed);
54+
Self {
55+
ptr: self.ptr,
56+
_marker: PhantomData,
57+
}
58+
}
59+
}
60+
```

src/arc-deref.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Deref
2+
3+
Alright. We now have a way to make, clone, and destroy `Arc`s, but how do we get
4+
to the data inside?
5+
6+
What we need now is an implementation of `Deref`.
7+
8+
We'll need to import the trait:
9+
```rust,ignore
10+
use std::ops::Deref;
11+
```
12+
13+
And here's the implementation:
14+
```rust,ignore
15+
impl<T> Deref for Arc<T> {
16+
type Target = T;
17+
18+
fn deref(&self) -> &T {
19+
&self.inner().data
20+
}
21+
}
22+
```
23+
24+
Pretty simple, eh? This simply dereferences the `NonNull` pointer to the
25+
`ArcInner<T>`, then gets a reference to the data inside.

src/arc-drop.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Dropping
2+
3+
We now need a way to decrease the reference count and drop the data once it is
4+
low enough, otherwise the data will live forever on the heap.
5+
6+
To do this, we can implement `Drop`.
7+
8+
Basically, we need to:
9+
1. Get the `ArcInner` value of the `Arc`
10+
2. Decrement the reference count
11+
3. If there is only one reference remaining to the data, then:
12+
4. Atomically fence the data to prevent reordering of the use and deletion of
13+
the data, then:
14+
5. Drop the inner data
15+
16+
Now, we need to decrement the reference count. We can also bring in step 3 by
17+
returning if the reference count is not equal to 1 (as `fetch_sub` returns the
18+
previous value):
19+
```rust,ignore
20+
if self.inner().rc.fetch_sub(1, Ordering::Release) != 1 {
21+
return;
22+
}
23+
```
24+
25+
We then need to create an atomic fence to prevent reordering of the use of the
26+
data and deletion of the data. As described in [the standard library's
27+
implementation of `Arc`][3]:
28+
> This fence is needed to prevent reordering of use of the data and deletion of
29+
> the data. Because it is marked `Release`, the decreasing of the reference
30+
> count synchronizes with this `Acquire` fence. This means that use of the data
31+
> happens before decreasing the reference count, which happens before this
32+
> fence, which happens before the deletion of the data.
33+
>
34+
> As explained in the [Boost documentation][1],
35+
>
36+
> > It is important to enforce any possible access to the object in one
37+
> > thread (through an existing reference) to *happen before* deleting
38+
> > the object in a different thread. This is achieved by a "release"
39+
> > operation after dropping a reference (any access to the object
40+
> > through this reference must obviously happened before), and an
41+
> > "acquire" operation before deleting the object.
42+
>
43+
> In particular, while the contents of an Arc are usually immutable, it's
44+
> possible to have interior writes to something like a Mutex<T>. Since a Mutex
45+
> is not acquired when it is deleted, we can't rely on its synchronization logic
46+
> to make writes in thread A visible to a destructor running in thread B.
47+
>
48+
> Also note that the Acquire fence here could probably be replaced with an
49+
> Acquire load, which could improve performance in highly-contended situations.
50+
> See [2].
51+
>
52+
> [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html
53+
> [2]: https://github.com/rust-lang/rust/pull/41714
54+
[3]: https://github.com/rust-lang/rust/blob/e1884a8e3c3e813aada8254edfa120e85bf5ffca/library/alloc/src/sync.rs#L1440-L1467
55+
56+
To do this, we do the following:
57+
```rust,ignore
58+
atomic::fence(Ordering::Acquire);
59+
```
60+
61+
We'll need to import `std::sync::atomic` itself:
62+
```rust,ignore
63+
use std::sync::atomic;
64+
```
65+
66+
Finally, we can drop the data itself. We use `Box::from_raw` to drop the boxed
67+
`ArcInner<T>` and its data. This takes a `*mut T` and not a `NonNull<T>`, so we
68+
must convert using `NonNull::as_ptr`.
69+
70+
```rust,ignore
71+
unsafe { Box::from_raw(self.ptr.as_ptr()); }
72+
```
73+
74+
This is safe as we know we have the last pointer to the `ArcInner` and that its
75+
pointer is valid.
76+
77+
Now, let's wrap this all up inside the `Drop` implementation:
78+
```rust,ignore
79+
impl<T> Drop for Arc<T> {
80+
fn drop(&mut self) {
81+
if self.inner().rc.fetch_sub(1, Ordering::Release) != 1 {
82+
return;
83+
}
84+
// This fence is needed to prevent reordering of the use and deletion
85+
// of the data.
86+
atomic::fence(Ordering::Acquire);
87+
// This is safe as we know we have the last pointer to the `ArcInner`
88+
// and that its pointer is valid.
89+
unsafe { Box::from_raw(self.ptr.as_ptr()); }
90+
}
91+
}
92+
```

src/arc-final.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Final Code
2+
3+
Here's the final code, with some added comments and re-ordered imports:
4+
```rust
5+
use std::marker::PhantomData;
6+
use std::ops::Deref;
7+
use std::ptr::NonNull;
8+
use std::sync::atomic::{self, AtomicUsize, Ordering};
9+
10+
pub struct Arc<T> {
11+
ptr: NonNull<ArcInner<T>>,
12+
_marker: PhantomData<ArcInner<T>>,
13+
}
14+
15+
pub struct ArcInner<T> {
16+
rc: AtomicUsize,
17+
data: T,
18+
}
19+
20+
impl<T> Arc<T> {
21+
pub fn new(data: T) -> Arc<T> {
22+
// We start the reference count at 1, as that first reference is the
23+
// current pointer.
24+
let boxed = Box::new(ArcInner {
25+
rc: AtomicUsize::new(1),
26+
data,
27+
});
28+
Arc {
29+
// It is okay to call `.unwrap()` here as we get a pointer from
30+
// `Box::into_raw` which is guaranteed to not be null.
31+
ptr: NonNull::new(Box::into_raw(boxed)).unwrap(),
32+
_marker: PhantomData,
33+
}
34+
}
35+
36+
fn inner(&self) -> &ArcInner<T> {
37+
// This unsafety is okay because while this Arc is alive, we're
38+
// guaranteed that the inner pointer is valid. Also, ArcInner<T> is
39+
// Sync if T is Sync.
40+
unsafe { self.ptr.as_ref() }
41+
}
42+
}
43+
44+
unsafe impl<T: Sync + Send> Send for Arc<T> {}
45+
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
46+
47+
impl<T> Clone for Arc<T> {
48+
fn clone(&self) -> Arc<T> {
49+
// Using a relaxed ordering is alright here as knowledge of the original
50+
// reference prevents other threads from wrongly deleting the object.
51+
self.inner().rc.fetch_add(1, Ordering::Relaxed);
52+
Self {
53+
ptr: self.ptr,
54+
_marker: PhantomData,
55+
}
56+
}
57+
}
58+
59+
impl<T> Drop for Arc<T> {
60+
fn drop(&mut self) {
61+
if self.inner().rc.fetch_sub(1, Ordering::Release) != 1 {
62+
return;
63+
}
64+
// This fence is needed to prevent reordering of the use and deletion
65+
// of the data.
66+
atomic::fence(Ordering::Acquire);
67+
// This is safe as we know we have the last pointer to the `ArcInner`
68+
// and that its pointer is valid.
69+
unsafe { Box::from_raw(self.ptr.as_ptr()); }
70+
}
71+
}
72+
73+
impl<T> Deref for Arc<T> {
74+
type Target = T;
75+
76+
fn deref(&self) -> &T {
77+
&self.inner().data
78+
}
79+
}
80+
```

0 commit comments

Comments
 (0)