In this blog post I will try to give you some clues on a question that a lot of people are asking me when talking about Rust:
They do the same sh*t, Rust is badly designed!
If you want to skip to the concise conclusion you should go to the TLDR.
If not you’ll surely miss a lot of important stuff in this article.
If you want to read about the ownership in Rust, check the Rust book
So let’s start!
I’ll try to expose why those methods attempt to do the same at first sight but can be used in different use cases where they act differently.
But first let’s see what Copying
and Cloning
mean. Those two terms represent the fact that we want to duplicate some data in some way. But we will see that some data can be simply copied and others need some more actions. To drive how the compiler should act we have access to two Trait
which are Copy
and Clone
. Each one represents a different behaviour.
In Rust Copy
and Clone
are traits which can be implemented by numerous types, it allows duplication/copy of objects/ref.
As defined in the Rust doc, Copy
trait is a marker (the full path of Copy
is std::marker::Copy
) and defines that the type’s values can be duplicated simply by copying bits.
As we can imagine not every type can implement Copy
, the best example is String
which implement Clone
but not Copy
.
String in detail
String
is defined by three components internally:
- A pointer to some bytes
- A length
- A capacity
(in fact all of those three components are a single one, a Vec<u8>
)
Because String
owns a pointer, we can’t use Copy
trait. It would end up copying the pointer which would lead to a double free at some point. That’s why String
can’t implement Copy
.
Following this rule, &mut T
can’t be Copy
even if T: Copy
because &mut
is a mutable reference.
fn implement_copy<T: Copy>() {}
fn main() {
implement_copy::<&u8>(); // Ok
implement_copy::<&mut u8>();
^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `&mut u8`
}
Another interesting thing is that every type that implements Copy
must implement Clone
. In fact, when a type implements Copy
the Clone
implementation is just returning *self
. This is because Clone
is a supertrait of Copy
.
But let’s see Clone
. As defined in the Rust documentation, Clone
is explicit, meaning that you need to explicitly ask for it. The compiler will not do it for you. Clone
exposes two methods clone
and clone_from
.
Another interesting thing to know about Clone
is that it returns its caller. Let’s see what it means:
|
|
We can see that the TypeIds are the same in both assertions meaning that we cloned the same type. Cloning &'static str
returns a &'static str
and a cloning a String
returns a String
. Perfect.
But what happens when we change clone
to to_owned
?
|
|
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `TypeId { t: 201574135764950547 }`,
right: `TypeId { t: 9147559743429524724 }`', src/main.rs:8:5
As we can see the TypeIds are not matching anymore for c1
, meaning that c1
is not a &'static str
.
Let’s add some type to check which type it is:
|
|
error[E0308]: mismatched types
--> src/main.rs:5:32
|
5 | let c1: &'static str = s1.to_owned();
| ------------ ^^^^^^^^^^^^^
| | |
| | expected `&str`, found struct `String`
| | help: consider borrowing here: `&s1.to_owned()`
| expected due to this
All right! The compiler is telling us that it found a String
where we asked for a &str
.
ToOwned
returns the Owned
version of a type. But what happens if we ask for a String
when calling clone
on a &'static str
?
|
|
error[E0308]: mismatched types
--> src/main.rs:5:22
|
5 | let c1: String = s1.clone();
| ------ ^^^^^^^^^^
| | |
| | expected struct `String`, found `&str`
| | help: try using a conversion method: `s1.to_string()`
| expected due to this
It is still not compiling because of mismatched types.
TLDR
So, to summarize, Clone
and ToOwned
are acting the same on type T
but they act differently on &T
.
Clone
returns its caller meaning that calling clone
on a &T
will give us a &T
(Clone
is not implemented for &mut T
obviously).
ToOwned
, on the other hand, will return the owned type of the caller meaning that calling to_owned
on &T
will give us a T
.
ToOwned
is also possible on mutable references &mut T
it will obviously give us a T
.
Another important point is that any type that implements Clone
also implements ToOwned
.