Concept
Every resource, be it an object, primitive or something else, is owned by its enclosing scope. The scope is responsible for cleaning the resource up if it is dropped (goes out of scope). This means that exactly one deallocation takes place, making common problems of manually managed languages, like double-free or forgetting to deallocate at all, pretty much impossible. However, this requires careful consideration on where to put data and how to make it available to another context from the programmer side, since just passing it around doesn't work in Rust in many cases. Let's look at an example:
struct MyStruct;
impl std::fmt::Display for MyStruct { .. }
fn consuming_function<T>(value: T) {
..
}
fn example() {
let primitive = 42;
let object = MyStruct;
consuming_function(primitive);
consuming_function(object);
// Fine
println!("{}", primitive);
// Compiler error!
println!("{}", object);
}
Now what happened here? It might be a bit unintuitive, but Rust handled the primitive and the struct object differently. This is caused by the Copy
trait that is implemented on the primitive, but not on our struct.
Implementing the trait causes Rust to use copy semantics for function parameters, you might know this as "call by value" or "pass by value".
On the other hand, not implementing it doesn't cause Rust to use reference semantics or "call by reference"/"pass by reference", since the language handles those explicitly.
Instead, it uses move semantics in that case.
So by calling consuming_function
, the ownership of the object variable is transferred into the function, making it no longer usable inside the function that owned it previously.
As the variable isn't returned by the function, it cleans the resource up at the end of its scope. If you use some unsafe manual memory management or have resources that need special cleanup, you can implement the Drop
trait for some custom cleanup code.
However, Drop
and Copy
are mutually exclusive since Copy
denotes a type with shallow copying and therefore no cleanup except for freeing the memory of the object itself.
Of course, we still want to be able to access data from multiple places, and that is where Borrowing comes into play.