Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Rust know which types own resources?

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?

like image 258
corazza Avatar asked Dec 05 '22 21:12

corazza


2 Answers

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.

like image 67
Vladimir Matveev Avatar answered Jan 17 '23 07:01

Vladimir Matveev


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();
}
like image 23
Adrian Avatar answered Jan 17 '23 07:01

Adrian