Reputation: 744
I thought rust makes another data on the heap memory when modifying the string. Therefore I expected a pointer address would change when I push a value to the string variable.
fn main() {
let mut hello = String::from("hello");
println!("{:?}", hello.as_ptr()); // 0x7fcfa7c01be0
hello.push_str(", world!");
println!("{:?}", hello.as_ptr()); // 0x7fcfa7c01be0
}
However, the result shows it's not. The address of the pointers was not changed, so I tested it with vector type.
fn main() {
let mut numbers = vec![1, 2, 3];
println!("{:?}", numbers.as_ptr()); // 0x7ffac4401be0
numbers.push(4);
println!("{:?}", numbers.as_ptr()); // 0x7ffac4401ce0
}
The pointer address of the vector variable was changed when modifying it. What is the difference between the memory of string and vector type?
Upvotes: 2
Views: 839
Reputation: 27955
A String
is like a Vec<T>
¹ in that it has both a len
gth and a capacity
. If the capacity of the current allocation is big enough to hold the new string, the underlying buffer does not need to be reallocated. The documentation for Vec<T>
explains it this way:
The capacity of a vector is the amount of space allocated for any future elements that will be added onto the vector. This is not to be confused with the length of a vector, which specifies the number of actual elements within the vector. If a vector's length exceeds its capacity, its capacity will automatically be increased, but its elements will have to be reallocated.
For example, a vector with capacity 10 and length 0 would be an empty vector with space for 10 more elements. Pushing 10 or fewer elements onto the vector will not change its capacity or cause reallocation to occur.
However, even if the capacity does change, the pointer value is still not guaranteed to move. The system allocator itself may be able to resize the allocation without moving it if there is enough unallocated space adjacent to it. That appears to be what's happening in your code. If you print the capacity along with the pointer, you can observe this behavior:
let mut hello = String::from("hello");
for _ in 0..10 {
println!("({:3}) {:?}", hello.capacity(), hello.as_ptr()); // 0x7fcfa7c01be0
hello.push_str(", world!");
}
( 5) 0x557624d8da40
( 13) 0x557624d8da40
( 26) 0x557624d8dba0
( 52) 0x557624d8dba0
( 52) 0x557624d8dba0
( 52) 0x557624d8dba0
(104) 0x557624d8dba0
(104) 0x557624d8dba0
(104) 0x557624d8dba0
(104) 0x557624d8dba0
In this example, the buffer was resized 4 times, but the contents were only moved once.
¹ Actually, a String
is a newtyped Vec<u8>
, which explains why they work the same.
Upvotes: 2
Reputation: 11232
Vec<T>
and String
may maintain extra space to avoid allocating on every push operation. This provides amortized O(1) time for push operations.
It happens to be the case that the vec!
macro is guaranteed to create a vector without such extra space, while String::from(&str)
does not have such a guarantee.
See https://doc.rust-lang.org/std/vec/struct.Vec.html#capacity-and-reallocation for more details.
Upvotes: 5