Skip to content

some work on the tutorials #6070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions doc/tutorial-ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>() -> T;
}

// a wrapper around the handle returned by the foreign code
pub struct Unique<T> {
priv ptr: *mut T
}

pub impl<'self, T: Owned> Unique<T> {
fn new(value: T) -> Unique<T> {
unsafe {
let ptr = malloc(core::sys::size_of::<T>() 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<T: Owned> Drop for Unique<T> {
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
Expand Down
132 changes: 33 additions & 99 deletions doc/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust doesn't really have objects, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, object is kind of an overloaded term - the C standard uses it too :). Perhaps "value" would be better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might just be my Ruby-brain talking. :D I see what way you're talking about it here.

are no dynamic failures from accessing freed resources. When a task fails, the
destructors of all objects in the task are called.

~~~~
use core::libc::{calloc, free, size_t};

fn main() {
unsafe {
let a = calloc(1, int::bytes as size_t);

let d;
The `~` sigil represents a unique handle for a memory allocation on the heap:

{
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);
}
}
~~~~

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
{
// an integer allocated on the heap
let y = ~10;
}
// the destructor frees the heap memory as soon as `y` goes out of scope
~~~~

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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down