From 721e1143a560e01694f88b53ddd9cfea1c6c7077 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Thu, 25 Apr 2013 21:36:02 -0400 Subject: [PATCH 1/2] tutorial: rework the section on destructors This removes the comparison to manual memory management examples, because it requires too much existing knowledge. Implementing custom destructors can be covered in the FFI tutorial, where `unsafe` is already well explained. --- doc/tutorial.md | 134 ++++++++++++------------------------------------ 1 file changed, 34 insertions(+), 100 deletions(-) diff --git a/doc/tutorial.md b/doc/tutorial.md index c10bc8a294c5..c757329a45f0 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -868,108 +868,27 @@ fn first((value, _): (int, float)) -> int { value } # Destructors -C-style resource management requires the programmer to match every allocation -with a free, which means manually tracking the responsibility for cleaning up -(the owner). Correctness is left to the programmer, and it's easy to get wrong. +A *destructor* is a function responsible for cleaning up the resources used by +an object when it is no longer accessible. Destructors can be defined to handle +the release of resources like files, sockets and heap memory. -The following code demonstrates manual memory management, in order to contrast -it with Rust's resource management. Rust enforces safety, so the `unsafe` -keyword is used to explicitly wrap the unsafe code. The keyword is a promise to -the compiler that unsafety does not leak outside of the unsafe block, and is -used to create safe concepts on top of low-level code. +Objects are never accessible after their destructor has been called, so there +are no dynamic failures from accessing freed resources. When a task fails, the +destructors of all objects in the task are called. + +The `~` sigil represents a unique handle for a memory allocation on the heap: ~~~~ -use core::libc::{calloc, free, size_t}; - -fn main() { - unsafe { - let a = calloc(1, int::bytes as size_t); - - let d; - - { - let b = calloc(1, int::bytes as size_t); - - let c = calloc(1, int::bytes as size_t); - d = c; // move ownership to d - - free(b); - } - - free(d); - free(a); - } +{ + // an integer allocated on the heap + let y = ~10; } +// the destructor frees the heap memory as soon as `y` goes out of scope ~~~~ -Rust uses destructors to handle the release of resources like memory -allocations, files and sockets. An object will only be destroyed when there is -no longer any way to access it, which prevents dynamic failures from an attempt -to use a freed resource. When a task fails, the stack unwinds and the -destructors of all objects owned by that task are called. - -The unsafe code from above can be contained behind a safe API that prevents -memory leaks or use-after-free: - -~~~~ -use core::libc::{calloc, free, c_void, size_t}; - -struct Blob { priv ptr: *c_void } - -impl Blob { - fn new() -> Blob { - unsafe { Blob{ptr: calloc(1, int::bytes as size_t)} } - } -} - -impl Drop for Blob { - fn finalize(&self) { - unsafe { free(self.ptr); } - } -} - -fn main() { - let a = Blob::new(); - - let d; - - { - let b = Blob::new(); - - let c = Blob::new(); - d = c; // move ownership to d - - // b is destroyed here - } - - // d is destroyed here - // a is destroyed here -} -~~~~ - -This pattern is common enough that Rust includes dynamically allocated memory -as first-class types (`~` and `@`). Non-memory resources like files are cleaned -up with custom destructors. - -~~~~ -fn main() { - let a = ~0; - - let d; - - { - let b = ~0; - - let c = ~0; - d = c; // move ownership to d - - // b is destroyed here - } - - // d is destroyed here - // a is destroyed here -} -~~~~ +Rust includes syntax for heap memory allocation in the language since it's +commonly used, but the same semantics can be implemented by a type with a +custom destructor. # Ownership @@ -984,6 +903,22 @@ and destroy the contained object when they go out of scope. A box managed by the garbage collector starts a new ownership tree, and the destructor is called when it is collected. +~~~~ +// the struct owns the objects contained in the `x` and `y` fields +struct Foo { x: int, y: ~int } + +{ + // `a` is the owner of the struct, and thus the owner of the struct's fields + let a = Foo { x: 5, y: ~10 }; +} +// when `a` goes out of scope, the destructor for the `~int` in the struct's +// field is called + +// `b` is mutable, and the mutability is inherited by the objects it owns +let mut b = Foo { x: 5, y: ~10 }; +b.x = 10; +~~~~ + If an object doesn't contain garbage-collected boxes, it consists of a single ownership tree and is given the `Owned` trait which allows it to be sent between tasks. Custom destructors can only be implemented directly on types @@ -1007,7 +942,7 @@ refer to that through a pointer. ## Owned boxes An owned box (`~`) is a uniquely owned allocation on the heap. It inherits the -mutability and lifetime of the owner as it would if there was no box. +mutability and lifetime of the owner as it would if there was no box: ~~~~ let x = 5; // immutable @@ -1021,8 +956,8 @@ let mut y = ~5; // mutable The purpose of an owned box is to add a layer of indirection in order to create recursive data structures or cheaply pass around an object larger than a -pointer. Since an owned box has a unique owner, it can be used to represent any -tree data structure. +pointer. Since an owned box has a unique owner, it can only be used to +represent a tree data structure. The following struct won't compile, because the lack of indirection would mean it has an infinite size: @@ -1092,7 +1027,6 @@ d = b; // box type is the same, okay c = b; // error ~~~~ - # Move semantics Rust uses a shallow copy for parameter passing, assignment and returning values From 195911fca412527472233aee163782c3bca11517 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Thu, 25 Apr 2013 22:08:29 -0400 Subject: [PATCH 2/2] tutorial-ffi: add example of a custom destructor --- doc/tutorial-ffi.md | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/doc/tutorial-ffi.md b/doc/tutorial-ffi.md index 6bebc6fd33a7..127f81589234 100644 --- a/doc/tutorial-ffi.md +++ b/doc/tutorial-ffi.md @@ -139,6 +139,74 @@ pub fn uncompress(src: &[u8]) -> Option<~[u8]> { For reference, the examples used here are also available as an [library on GitHub](https://github.com/thestinger/rust-snappy). +# Destructors + +Foreign libraries often hand off ownership of resources to the calling code, +which should be wrapped in a destructor to provide safety and guarantee their +release. + +A type with the same functionality as owned boxes can be implemented by +wrapping `malloc` and `free`: + +~~~~ +use core::libc::{c_void, size_t, malloc, free}; + +#[abi = "rust-intrinsic"] +extern "rust-intrinsic" mod rusti { + fn init() -> T; +} + +// a wrapper around the handle returned by the foreign code +pub struct Unique { + priv ptr: *mut T +} + +pub impl<'self, T: Owned> Unique { + fn new(value: T) -> Unique { + unsafe { + let ptr = malloc(core::sys::size_of::() as size_t) as *mut T; + assert!(!ptr::is_null(ptr)); + *ptr = value; + Unique{ptr: ptr} + } + } + + // the 'self lifetime results in the same semantics as `&*x` with ~T + fn borrow(&self) -> &'self T { + unsafe { cast::transmute(self.ptr) } + } + + // the 'self lifetime results in the same semantics as `&mut *x` with ~T + fn borrow_mut(&mut self) -> &'self mut T { + unsafe { cast::transmute(self.ptr) } + } +} + +#[unsafe_destructor] +impl Drop for Unique { + fn finalize(&self) { + unsafe { + let mut x = rusti::init(); // dummy value to swap in + x <-> *self.ptr; // moving the object out is needed to call the destructor + free(self.ptr as *c_void) + } + } +} + +// A comparison between the built-in ~ and this reimplementation +fn main() { + { + let mut x = ~5; + *x = 10; + } // `x` is freed here + + { + let mut y = Unique::new(5); + *y.borrow_mut() = 10; + } // `y` is freed here +} +~~~~ + # Linking In addition to the `#[link_args]` attribute for explicitly passing arguments to the linker, an