When one has a box pointer to some heap-allocated memory, I assume that Rust has 'hardcoded' knowledge of ownership, so that when ownership is transferred by calling some function, the resources are moved and the argument in the function is the new owner.
However, how does this happen for vectors for example? They too 'own' their resources, and ownership mechanics apply like for box pointers -- yet they are regular values stored in variables themselves, and not pointers. How does Rust (know to) apply ownership mechanics in this situation?
Can I make my own type which owns resources?
tl;dr: "owning" types in Rust are not some magic and they are most certainly not hardcoded into the compiler or language. They are just types which written in a certain way (do not implement Copy
and likely have a destructor) and have certain semantics which is enforced through non-copyability and the destructor.
In its core Rust's ownership mechanism is very simple and has very simple rules.
First of all, let's define what move is. It is simple - a value is said to be moved when it becomes available under a new name and stops being available under the old name:
struct X(u32);
let x1 = X(12);
let x2 = x1;
// x1 is no longer accessible here, trying to use it will cause a compiler error
Same thing happens when you pass a value into a function:
fn do_something(x: X) {}
let x1 = X(12);
do_something(x1);
// x1 is no longer accessible here
Note that there is absolutely no magic here - it is just that by default every value of every type behaves like in the above examples. Values of each struct or enum you or someone else creates by default will be moved.
Another important thing is that you can give every type a destructor, that is, a piece of code which is invoked when the value of this type goes out of scope and destroyed. For example, destructors associated with Vec
or Box
will free the corresponding piece of memory. Destructors can be declared by implementing Drop
trait:
struct X(u32);
impl Drop for X {
fn drop(&mut self) {
println!("Dropping {}", x.0);
}
}
{
let x1 = X(12);
} // x1 is dropped here, and "Dropping 12" will be printed
There is a way to opt-out of non-copyability by implementing Copy
trait which marks the type as automatically copyable - its values will no longer be moved but copied:
#[derive(Copy, Clone)] struct X(u32);
let x1 = X(12);
let x2 = x1;
// x1 is still available here
The copy is done bytewise - x2
will contain a byte-identical copy of x1
.
Not every type can be made Copy
- only those which have Copy
interior and do not implement Drop
. All primitive types (except &mut
references but including *const
and *mut
raw pointers) are Copy
in Rust, so each struct which contains only primitives can be made Copy
. On the other hand, structs like Vec
or Box
are not Copy
- they deliberately do not implement it because bytewise copy of them will lead to double frees because their destructors can be run twice over the same pointer.
The Copy
bit above is a slight digression on my side, just to give a clearer picture. Ownership in Rust is based on move semantics. When we say that some value own something, like in "Box<T>
owns the given T
", we mean semantic connection between them, not something magical or something which is built into the language. It is just most such values like Vec
or Box
do not implement Copy
and thus moved instead of copied, and they also (optionally) have a destructor which cleans up anything these types may have allocated for them (memory, sockets, files, etc.).
Given the above, of course you can write your own "owning" types. This is one of the cornerstones of idiomatic Rust, and a lot of code in the standard library and external libraries is written in such way. For example, some C APIs provide functions for creating and destroying objects. Writing an "owning" wrapper around them is very easy in Rust and it is probably very close to what you're asking for:
extern {
fn create_widget() -> *mut WidgetStruct;
fn destroy_widget(w: *mut WidgetStruct);
fn use_widget(w: *mut WidgetStruct) -> u32;
}
struct Widget(*mut WidgetStruct);
impl Drop for Widget {
fn drop(&mut self) {
unsafe { destroy_widget(self.0); }
}
}
impl Widget {
fn new() -> Widget { Widget(unsafe { create_widget() }) }
fn use_it(&mut self) -> u32 {
unsafe { use_widget(self.0) }
}
}
Now you can say that Widget
owns some foreign resource represented by *mut WidgetStruct
.
Here is another example of how a value might own memory and free it when the value is destroyed:
extern crate libc;
use libc::{malloc, free, c_void};
struct OwnerOfMemory {
ptr: *mut c_void
}
impl OwnerOfMemory {
fn new() -> OwnerOfMemory {
OwnerOfMemory {
ptr: unsafe { malloc(128) }
}
}
}
impl Drop for OwnerOfMemory {
fn drop(&mut self) {
unsafe { free(self.ptr); }
}
}
fn main() {
let value = OwnerOfMemory::new();
}
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