Skip to content

tutorial: rewrite the sections on boxes/moves #9589

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

Merged
merged 1 commit into from
Sep 29, 2013
Merged
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
128 changes: 70 additions & 58 deletions doc/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -923,22 +923,60 @@ custom destructors.

# Boxes

Many modern languages represent values as pointers to heap memory by
default. In contrast, Rust, like C and C++, represents such types directly.
Another way to say this is that aggregate data in Rust are *unboxed*. This
means that if you `let x = Point { x: 1f, y: 1f };`, you are creating a struct
on the stack. If you then copy it into a data structure, you copy the entire
struct, not just a pointer.
A value in Rust is stored directly inside the owner. If a `struct` contains
four `int` fields, it will be four times as large as a single `int`. The
following `struct` type is invalid, as it would have an infinite size:

For small structs like `Point`, this is usually more efficient than allocating
memory and indirecting through a pointer. But for big structs, or mutable
state, it can be useful to have a single copy on the stack or on the heap, and
refer to that through a pointer.
~~~~ {.xfail-test}
struct List {
next: Option<List>,
data: int
}
~~~~

> ***Note:*** The `Option` type is an enum representing an *optional* value.
> It's comparable to a nullable pointer in many other languages, but stores the
> contained value unboxed.

An *owned box* (`~`) uses a heap allocation to provide the invariant of always
Copy link
Member

Choose a reason for hiding this comment

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

It's a little peculiar that the first mention of "box" in the box section is its the 4th paragraph (but not a big issue).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's intended to clearly introduce the purpose of them before getting into the jargon. I had the same examples there before, but apparently no one got to reading them.

being the size of a pointer, regardless of the contained type. This can be
leveraged to create a valid recursive `struct` type with a finite size:

~~~~
struct List {
next: Option<~List>,
data: int
}
~~~~

Since an owned box has a single owner, they are limited to representing
tree-like data structures.

The most common use case for owned boxes is creating recursive data structures
like a binary search tree. Rust's trait-based generics system (covered later in
the tutorial) is usually used for static dispatch, but also provides dynamic
dispatch via boxing. Values of different types may have different sizes, but a
box is able to *erase* the difference via the layer of indirection they
provide.

## Owned boxes
In uncommon cases, the indirection can provide a performance gain or memory
reduction by making values smaller. However, unboxed values should almost
always be preferred.

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:
Note that returning large unboxed values via boxes is unnecessary. A large
value is returned via a hidden output parameter, and the decision on where to
place the return value should be left to the caller:

~~~~
fn foo() -> (int, int, int, int, int, int) {
(5, 5, 5, 5, 5, 5)
}

let x = ~foo(); // allocates, and writes the integers directly to it
~~~~

Beyond the properties granted by the size, an owned box behaves as a regular
Copy link
Member

Choose a reason for hiding this comment

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

and the addition of a destructor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added below now

value by inheriting the mutability and lifetime of the owner:

~~~~
let x = 5; // immutable
Expand All @@ -950,35 +988,33 @@ let mut y = ~5; // mutable
*y += 2; // the * operator is needed to access the contained value
~~~~

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 only be used to
represent a tree data structure.
As covered earlier, an owned box has a destructor to clean up the allocated
memory. This makes it more restricted than an unboxed type with no destructor
by introducing *move semantics*.

The following struct won't compile, because the lack of indirection would mean
it has an infinite size:
# Move semantics

~~~~ {.xfail-test}
struct Foo {
child: Option<Foo>
}
~~~~
Rust uses a shallow copy for parameter passing, assignment and returning from
functions. This is considered a move of ownership for types with destructors.
Copy link
Member

Choose a reason for hiding this comment

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

Should &fn and &mut T be mentioned here?

Copy link
Member

Choose a reason for hiding this comment

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

(and the jargon "implicitly copyable": e.g. "(types that don't move in this manner are called 'implicitly copyable').")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't cover &fn and &mut here because they're not introduced. The implicitly copyable jargon is not good because we just explained moves as shallow copies transferring ownership. I don't see another way to do it, and I don't think the terminology is correct.

After a value has been moved, it can no longer be used from the source location
and will not be destroyed when the source goes out of scope.

> ***Note:*** The `Option` type is an enum that represents an *optional* value.
> It's comparable to a nullable pointer in many other languages, but stores the
> contained value unboxed.
~~~~
let x = ~5;
let y = x.clone(); // y is a newly allocated box
Copy link
Member

Choose a reason for hiding this comment

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

If I remember right, this does actually coerce the ~int to a &int and thus return a int right now.

Copy link
Member

Choose a reason for hiding this comment

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

@Kimundi you don't remember correctly :P this code means y: ~int.

let z = x; // no new memory allocated, x can no longer be used
~~~~

Adding indirection with an owned pointer allocates the child outside of the
struct on the heap, which makes it a finite size and won't result in a
compile-time error:
The mutability of a value may be changed by moving it to a new owner:

~~~~
struct Foo {
child: Option<~Foo>
}
let r = ~13;
let mut s = r; // box becomes mutable
*s += 1;
let t = s; // box becomes immutable
~~~~

## Managed boxes
# Managed boxes

A managed box (`@`) is a heap allocation with the lifetime managed by a
task-local garbage collector. It will be destroyed at some point after there
Expand Down Expand Up @@ -1023,30 +1059,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 functions. A shallow copy is considered a move of ownership if the
ownership tree of the copied value includes an owned box or a type with a
custom destructor. After a value has been moved, it can no longer be used from
the source location and will not be destroyed there.

~~~~
let x = ~5;
let y = x.clone(); // y is a newly allocated box
let z = x; // no new memory allocated, x can no longer be used
~~~~

Since in owned boxes mutability is a property of the owner, not the
box, mutable boxes may become immutable when they are moved, and vice-versa.

~~~~
let r = ~13;
let mut s = r; // box becomes mutable
*s += 1;
let t = s; // box becomes immutable
~~~~

# Borrowed pointers

Rust's borrowed pointers are a general purpose reference type. In contrast with
Expand Down