I'm wondering what the difference is between:
"some string".to_string()
And
"some string".into_string()
The former seems to come from ToString, which is quite clear.
However, the latter seems to comes from IntoString, which is less clear to me.
What does it mean to consume a value ? What is the difference between the two traits ?
Additional information after doing some digging around.
Here's the current implementation of into_string for a String. As you can see, it only returns itself, so no allocation is done.
What does it mean to
consumea value?
Consuming a value has to do with moving a value. Before I discuss the differences between the two traits, I'll give some examples of what it means to move a value. Let's create a Vec of Ascii characters: asciis.
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
println!("{}", asciis);
}
Internally, the Vec is a struct with three fields:
Vec.Vec.Vec.Pictorially, the memory layout of the Vec and the data it is managing may look something like this.
Stack: asciis Heap:
+----------+ +----------+
0xF0 | data | ----> 0xA0 | 'h' |
+----------+ +----------+
0xF4 | length | 0xA1 | 'i' |
+----------+ +----------+
0xF8 | capacity |
+----------+
When our Vec goes out of scope, it frees the memory it's managing. The freed memory is garbage to us. It would be erroneous to access freed memory. This would look something like the following. The Vec is gone and the memory on the heap has been freed.
Heap:
+----------+
0xA0 | GARBAGE |
+----------+
0xA1 | GARBAGE |
+----------+
Now, let's go back to our code and try to make a copy of asciis.
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
{
let an_attempted_copy = asciis;
}
println!("{}", asciis);
}
Let's guess that an_attempted_copy is a copy of asciis. After we make the copy, our memory may look something like the following.
Stack: asciis Heap: Stack: an_attempted_copy
+----------+ +----------+ +----------+
0xF0 | data | ----> 0xA0 | 'h' | <---- 0xE0 | data |
+----------+ +----------+ +----------+
0xF4 | length | 0xA1 | 'i' | | length |
+----------+ +----------+ +----------+
0xF8 | capacity | | capacity |
+----------+ +----------+
Right before we try to println! asciis, an_attempted_copy goes out of scope! Just like before, the data pointed to by our Vec is freed.
Stack: asciis Heap:
+----------+ +----------+
0xF0 | data | ----> 0xA0 | GARBAGE |
+----------+ +----------+
0xF4 | length | 0xA1 | GARBAGE |
+----------+ +----------+
0xF8 | capacity |
+----------+
Uh oh, asciis is pointing into freed memory! This is bad news since we're just about to println! asciis.
So how would we remedy the situation? Well, here's two options.
asciis into an_attempted_copy, we could copy the data pointed to by asciis into a freshly allocated piece of memory. Other languages like C++ do this.asciis, we can move it! This is what rust does. So what does it mean to move? It means that an_attempted_copy will take ownership of the data previously pointed to by asciis. asciis loses ownership and we can't use it anymore. Let's rename an_attempted_copy for clarity.
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
{
let actually_a_move = asciis;
}
println!("{}", asciis);
}
Now, let's draw our memory layout right after we move into actually_a_move.
Stack: asciis Heap: Stack: actually_a_move
+----------+ +----------+ +----------+
0xF0 | GARBAGE | 0xA0 | 'h' | <---- 0xE0 | data |
+----------+ +----------+ +----------+
0xF4 | GARBAGE | 0xA1 | 'i' | | length |
+----------+ +----------+ +----------+
0xF8 | GARBAGE | | capacity |
+----------+ +----------+
asciis doesn't own the memory anymore, so we can't use asciis anymore. This means it's pretty much garbage. So if we can't use asciis anymore, what happens when we println! it? We get the following error.
<anon>:6:24: 6:30 error: use of moved value: `asciis`
<anon>:6 println!("{}", asciis);
^~~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
<anon>:6:9: 6:32 note: expansion site
<anon>:4:17: 4:32 note: `asciis` moved here because it has type `collections::vec::Vec<std::ascii::Ascii>`, which is moved by default (use `ref` to override)
<anon>:4 let actually_a_move = asciis;
^~~~~~~~~~~~~~~
error: aborting due to previous error
As expected, the rust compiler is telling us we were trying to use ascii, but ascii was a moved value; this is erroneous.
Move semantics (and related topics like borrowing and lifetimes) is tough stuff. I've only barely scratched the surface here. For more information, rust by example and this stackoverflow question are both good resources.
to_string vs into_string
What is the difference between the two traits?
Now that I've explored the concept of consuming or moving a value, let's get to the differences between the two traits. Let's first look at the type signature of to_string.
fn to_string(&self) -> String;
This function takes a reference to self and returns a fresh String for us to use. I haven't discussed references and how they affect movement, but trust me when I say no moving is done here.
Now let's look at the type signature of into_string.
fn into_string(self) -> String;
This function does not take a reference to self. Instead, self is moved into the function.
So what are the implications of this difference? Let's take a look at an example.
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
let no_moves_here = asciis.to_string();
println!("{}", asciis);
}
We again create a Vec of Ascii characters. Then, when we call asciis.to_string(), a brand new String is created and asciis is never moved. This code will build and run as you expect, printing out [h, i]. Now, let's use into_string.
fn main() {
let asciis = vec!['h'.to_ascii(), 'i'.to_ascii()];
let uh_oh_we_just_moved_asciis = asciis.into_string();
println!("{}", asciis);
}
Here's the error message we get when trying to build this code.
<anon>:4:24: 4:30 error: use of moved value: `asciis`
<anon>:4 println!("{}", asciis);
^~~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
<anon>:4:9: 4:32 note: expansion site
<anon>:3:42: 3:48 note: `asciis` moved here because it has type `collections::vec::Vec<std::ascii::Ascii>`, which is non-copyable (perhaps you meant to use clone()?)
<anon>:3 let uh_oh_we_just_moved_asciis = asciis.into_string();
^~~~~~
error: aborting due to previous error
So what happened? Well asciis is being moved into the function into_string. Just like the last time we tried to use asciis after we moved it, the rust compiler will reject our code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With