4

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?

trincot
  • 317,000
  • 35
  • 244
  • 286
corazza
  • 31,222
  • 37
  • 115
  • 186
  • I'm not sure I understand your question, but when you put a value into a vector, the value is then owned by the vector. I think it would be helpful if you provided a code example of what you are asking. – Adrian Jul 22 '15 at 16:01
  • 2
    I don't have the time to write a full answer now, I just want to mention that `Box` is not special or hardcoded. (Well, currently some aspects are, but none of those have anything to do with this question, and they're only hardcoded because the language features to express these things in pure library code aren't finished yet.) Ownership for Box works exactly as ownership for Vec. –  Jul 22 '15 at 16:03
  • @Adrian *"but when you put a value into a vector, the value is then owned by the vector."* AFAIK values aren't owned, *resources* are. I'm not asking about the data in the vector, I'm asking about the fact that the vector variable *owns memory*, just like a box does -- but it is not a box. I'm basically just asking about Rust internals, i.e. to which constructs does ownership apply, and how is that determined. – corazza Jul 22 '15 at 16:03
  • @delnan I thought ownership had to be 'baked in' the language? If you don't have time to explain that, do you maybe have a link that does? – corazza Jul 22 '15 at 16:04
  • When you create a new vector (`Vec::new`) or push to one, memory is allocated by the vector, for example on [this line](https://doc.rust-lang.org/src/collections/vec.rs.html#222). `Vec` implements `Drop`, which enables the memory to be freed when the vector is destroyed, which happens on [this line](https://doc.rust-lang.org/src/collections/vec.rs.html#1637). – Adrian Jul 22 '15 at 16:10
  • @jco In a sense, the concept of ownership is baked in the language, but as a general mechanism which knows nothing about specific types and is leveraged in various ways by various library types. Sadly I don't have a good and short explanation at hand either. –  Jul 22 '15 at 16:10
  • @delnan Aha, OK, it's a bit clearer now. However it still has to depend on the type, i.e. why are boxed ints owned and regular ints just copied around? Clearly it only applies to types which are *somehow* connected to resources -- either directly pointing to them (as box), or indirectly contain a pointer (as vector I presume, because it also has a POD part for length and capacity). – corazza Jul 22 '15 at 16:17

2 Answers2

14

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.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • 4
    Note that the set of primitive types also includes raw pointers `* mut T` and `* const T`, which are used in the implementation of `Box` and `Vec` and other container types. If not for the `Drop` impl, `Box` and `Vec` could totally be `Copy` - it would just be `unsafe` and semantically wrong. –  Jul 22 '15 at 16:22
  • 3
    Since it often trips people up, note that moves and copies are identical at runtime - only the type-checker knows the difference. Both end up as a shallow `memcpy`. – Veedrac Jul 22 '15 at 17:06
  • @VladimirMatveev I've got a [new question](http://stackoverflow.com/q/31597416/924313) about borrowing and `drop`, if you'd be interested :) – corazza Jul 23 '15 at 20:29
2

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();
}
Adrian
  • 14,931
  • 9
  • 45
  • 70