mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-03 07:48:41 +08:00
remove the fun stuff
This commit is contained in:
parent
57fd1a575c
commit
a47516a171
@ -40,191 +40,8 @@ values like this is very common, and so there's a macro to do it for us:
|
||||
let v = vec![5, 6, 7, 8];
|
||||
```
|
||||
|
||||
This macro does the exact same thing as our previous example, but it's much
|
||||
more convenient.
|
||||
|
||||
How does this all work? Under the hood, vectors look approximately like this:
|
||||
|
||||
```rust,ignore
|
||||
struct Vec<T> {
|
||||
data: &mut T,
|
||||
capacity: usize,
|
||||
length: usize,
|
||||
}
|
||||
```
|
||||
|
||||
This is not literally true, but will help you gain some intutions about it. The
|
||||
actual representation is quite involved, and you can [read a chapter in the
|
||||
Nomicon][nomicon] for the full details.
|
||||
|
||||
[nomicon]: https://doc.rust-lang.org/stable/nomicon/vec.html
|
||||
|
||||
At a high level, though, this is okay: a vector has a reference to some data,
|
||||
a capacity, and a length. Let's go through these lines of code and see how
|
||||
the vector changes.
|
||||
|
||||
```rust
|
||||
let mut v = Vec::new();
|
||||
#
|
||||
# v.push(5);
|
||||
# v.push(6);
|
||||
# v.push(7);
|
||||
# v.push(8);
|
||||
```
|
||||
|
||||
We've created our new vector. It will look like this:
|
||||
|
||||
```text
|
||||
Vec<i32> {
|
||||
data: <invalid>,
|
||||
capacity: 0,
|
||||
length: 0,
|
||||
}
|
||||
```
|
||||
|
||||
We don't have anything stored yet, so the vector hasn't made any room for any
|
||||
elements. Since we don't have any elements, `data` isn't valid either.
|
||||
|
||||
```rust
|
||||
# let mut v = Vec::new();
|
||||
#
|
||||
v.push(5);
|
||||
# v.push(6);
|
||||
# v.push(7);
|
||||
# v.push(8);
|
||||
```
|
||||
|
||||
Next, we insert one value into the vector. `push` looks at the current capacity
|
||||
and length, and sees that there's no room for this `5` to go. Since there's
|
||||
currently room for zero elements, it will request enough memory from the
|
||||
operating system for a single element, and then copy `5` into that memory. It
|
||||
then updates the internal details, and now they look like this:
|
||||
|
||||
```text
|
||||
struct Vec<T> {
|
||||
data: <address of first element>,
|
||||
capacity: 1,
|
||||
length: 1,
|
||||
}
|
||||
```
|
||||
|
||||
Our `data` now points to the start of this memory, and `capacity` and `length`
|
||||
are both set to one. If they're both set to the same value, what's the
|
||||
difference? We will see that shortly. But first, let's insert another value
|
||||
into the vector:
|
||||
|
||||
|
||||
```rust
|
||||
# let mut v = Vec::new();
|
||||
#
|
||||
# v.push(5);
|
||||
v.push(6);
|
||||
# v.push(7);
|
||||
# v.push(8);
|
||||
```
|
||||
|
||||
Same thing, we `push` a `6`. In this case, the same thing will happen:
|
||||
`push` looks at the values of `capacity` and `len`, and sees that we don't have
|
||||
room for a second element. So here's what it does: it requests room for twice
|
||||
as many elements as we currently have. In this case, that means two elements.
|
||||
Once it gets the memory it requested, the code in `push` copies the existing
|
||||
element over into the new memory allocation then copies the `6` into the next
|
||||
open slot. After updating the internal values, the vector now looks like this:
|
||||
|
||||
```text
|
||||
struct Vec<T> {
|
||||
data: <address of first element>,
|
||||
capacity: 2,
|
||||
length: 2,
|
||||
}
|
||||
```
|
||||
|
||||
Our `capacity` and `length` both show two here. Let's try inserting another
|
||||
element into the vector:
|
||||
|
||||
```rust
|
||||
# let mut v = Vec::new();
|
||||
#
|
||||
# v.push(5);
|
||||
# v.push(6);
|
||||
v.push(7);
|
||||
# v.push(8);
|
||||
```
|
||||
|
||||
Same story here: `push` looks and sees that our vector is full. However,
|
||||
this time, something is slightly different. We currently have a capacity of
|
||||
two elements, so the vector will request memory for double that number of
|
||||
elements from the operating system: four. But why does it double every time?
|
||||
We'll learn about that soon. For now, `push` will then copy the first two
|
||||
elements over to the new memory, copy our `7` onto the end, and then update
|
||||
the internal state. What's it look like now?
|
||||
|
||||
```text
|
||||
struct Vec<T> {
|
||||
data: <address of first element>,
|
||||
capacity: 4,
|
||||
length: 3,
|
||||
}
|
||||
```
|
||||
|
||||
Ah ha! A difference. In essence, `capacity` tracks how much memory we've got
|
||||
saved away for holding elements. `length` keeps track of how many elements we
|
||||
are actually storing. Why not just allocate exactly as much as we need? In order
|
||||
to get more heap memory, we have to talk to the operating system, and that
|
||||
takes time. Not only that, but each time we get chunk of memory, we have to
|
||||
copy the contents of the vector from the old memory to the new memory. So we
|
||||
make a tradeoff: we request a bit more memory than we need, but in exchange,
|
||||
we speed up the next few `push` operations.
|
||||
|
||||
We can see this speed the next time we call `push`:
|
||||
|
||||
```rust
|
||||
# let mut v = Vec::new();
|
||||
#
|
||||
# v.push(5);
|
||||
# v.push(6);
|
||||
v.push(7);
|
||||
# v.push(8);
|
||||
```
|
||||
|
||||
Now, `push` looks at the capacity versus the length: we have room for four
|
||||
elements, but we only have three elements. Perfect! We skip that "request more
|
||||
memory from the OS and copy everything" business and just copy the `7` into
|
||||
the existing memory. After updating `capacity` and `len`, it looks like this:
|
||||
|
||||
```text
|
||||
struct Vec<T> {
|
||||
data: <address of first element>,
|
||||
capacity: 4,
|
||||
length: 4,
|
||||
}
|
||||
```
|
||||
|
||||
We can do even better than this, though. While `new` allocates a new empty
|
||||
vector, we can use `with_capacity` if we know how many things we're going to
|
||||
insert:
|
||||
|
||||
```rust
|
||||
let mut v = Vec::with_capacity(4);
|
||||
#
|
||||
# v.push(5);
|
||||
# v.push(6);
|
||||
# v.push(7);
|
||||
# v.push(8);
|
||||
```
|
||||
|
||||
Here, our initial vector looks like this:
|
||||
|
||||
```text
|
||||
struct Vec<T> {
|
||||
data: <invalid>,
|
||||
capacity: 4,
|
||||
length: 0,
|
||||
}
|
||||
```
|
||||
|
||||
Now, when we do our `push`es, we won't need to allocate until we `push` our
|
||||
fifth element. The `vec!` macro uses a similar trick, so don't worry about it!
|
||||
This macro does a similar thing to our previous example, but it's much more
|
||||
convenient.
|
||||
|
||||
## Destroying a vector
|
||||
|
||||
@ -302,7 +119,7 @@ note: immutable borrow ends here
|
||||
error: aborting due to previous error(s)
|
||||
```
|
||||
|
||||
What's the matter here? Remember what can happen when we `push` to a vector: it
|
||||
might have to go get more memory and copy all of the values to that new
|
||||
memory. If it has to do this, our `first` would be pointing to old, invalid
|
||||
memory!
|
||||
Why is this an error? Due to the way vectors work, adding a new element onto
|
||||
the end might require allocating new memory and copying the old elements over.
|
||||
If this happened, our reference would be pointing to deallocated memory. For
|
||||
more on this, see the Nomicon: https://doc.rust-lang.org/stable/nomicon/vec.html
|
||||
|
Loading…
Reference in New Issue
Block a user