]> git.proxmox.com Git - rustc.git/blame - src/doc/nomicon/src/arc-clone.md
New upstream version 1.54.0+dfsg1
[rustc.git] / src / doc / nomicon / src / arc-clone.md
CommitLineData
5869c6ff
XL
1# Cloning
2
3Now that we've got some basic code set up, we'll need a way to clone the `Arc`.
4
5Basically, we need to:
61. Increment the atomic reference count
72. Construct a new instance of the `Arc` from the inner pointer
8
9First, we need to get access to the `ArcInner`:
10```rust,ignore
11let inner = unsafe { self.ptr.as_ref() };
12```
13
14We can update the atomic reference count as follows:
15```rust,ignore
16let old_rc = inner.rc.fetch_add(1, Ordering::???);
17```
18
19But what ordering should we use here? We don't really have any code that will
20need atomic synchronization when cloning, as we do not modify the internal value
21while cloning. Thus, we can use a Relaxed ordering here, which implies no
22happens-before relationship but is atomic. When `Drop`ping the Arc, however,
23we'll need to atomically synchronize when decrementing the reference count. This
24is described more in [the section on the `Drop` implementation for
25`Arc`](arc-drop.md). For more information on atomic relationships and Relaxed
26ordering, see [the section on atomics](atomics.md).
27
28Thus, the code becomes this:
29```rust,ignore
30let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
31```
32
33We'll need to add another import to use `Ordering`:
34```rust,ignore
35use std::sync::atomic::Ordering;
36```
37
38However, we have one problem with this implementation right now. What if someone
39decides to `mem::forget` a bunch of Arcs? The code we have written so far (and
40will write) assumes that the reference count accurately portrays how many Arcs
41are in memory, but with `mem::forget` this is false. Thus, when more and more
42Arcs are cloned from this one without them being `Drop`ped and the reference
43count being decremented, we can overflow! This will cause use-after-free which
44is **INCREDIBLY BAD!**
45
46To handle this, we need to check that the reference count does not go over some
47arbitrary value (below `usize::MAX`, as we're storing the reference count as an
48`AtomicUsize`), and do *something*.
49
50The standard library's implementation decides to just abort the program (as it
51is an incredibly unlikely case in normal code and if it happens, the program is
52probably incredibly degenerate) if the reference count reaches `isize::MAX`
53(about half of `usize::MAX`) on any thread, on the assumption that there are
54probably not about 2 billion threads (or about **9 quintillion** on some 64-bit
55machines) incrementing the reference count at once. This is what we'll do.
56
57It's pretty simple to implement this behaviour:
58```rust,ignore
59if old_rc >= isize::MAX as usize {
60 std::process::abort();
61}
62```
63
64Then, we need to return a new instance of the `Arc`:
65```rust,ignore
66Self {
67 ptr: self.ptr,
68 phantom: PhantomData
69}
70```
71
72Now, let's wrap this all up inside the `Clone` implementation:
73```rust,ignore
74use std::sync::atomic::Ordering;
75
76impl<T> Clone for Arc<T> {
77 fn clone(&self) -> Arc<T> {
78 let inner = unsafe { self.ptr.as_ref() };
79 // Using a relaxed ordering is alright here as we don't need any atomic
80 // synchronization here as we're not modifying or accessing the inner
81 // data.
82 let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
83
84 if old_rc >= isize::MAX as usize {
85 std::process::abort();
86 }
87
88 Self {
89 ptr: self.ptr,
90 phantom: PhantomData,
91 }
92 }
93}
94```