Skip to content

Commit 721e114

Browse files
committed
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.
1 parent 0604468 commit 721e114

File tree

1 file changed

+33
-99
lines changed

1 file changed

+33
-99
lines changed

doc/tutorial.md

Lines changed: 33 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -868,108 +868,27 @@ fn first((value, _): (int, float)) -> int { value }
868868

869869
# Destructors
870870

871-
C-style resource management requires the programmer to match every allocation
872-
with a free, which means manually tracking the responsibility for cleaning up
873-
(the owner). Correctness is left to the programmer, and it's easy to get wrong.
871+
A *destructor* is a function responsible for cleaning up the resources used by
872+
an object when it is no longer accessible. Destructors can be defined to handle
873+
the release of resources like files, sockets and heap memory.
874874

875-
The following code demonstrates manual memory management, in order to contrast
876-
it with Rust's resource management. Rust enforces safety, so the `unsafe`
877-
keyword is used to explicitly wrap the unsafe code. The keyword is a promise to
878-
the compiler that unsafety does not leak outside of the unsafe block, and is
879-
used to create safe concepts on top of low-level code.
875+
Objects are never accessible after their destructor has been called, so there
876+
are no dynamic failures from accessing freed resources. When a task fails, the
877+
destructors of all objects in the task are called.
880878

881-
~~~~
882-
use core::libc::{calloc, free, size_t};
883-
884-
fn main() {
885-
unsafe {
886-
let a = calloc(1, int::bytes as size_t);
887-
888-
let d;
879+
The `~` sigil represents a unique handle for a memory allocation on the heap:
889880

890-
{
891-
let b = calloc(1, int::bytes as size_t);
892-
893-
let c = calloc(1, int::bytes as size_t);
894-
d = c; // move ownership to d
895-
896-
free(b);
897-
}
898-
899-
free(d);
900-
free(a);
901-
}
902-
}
903881
~~~~
904-
905-
Rust uses destructors to handle the release of resources like memory
906-
allocations, files and sockets. An object will only be destroyed when there is
907-
no longer any way to access it, which prevents dynamic failures from an attempt
908-
to use a freed resource. When a task fails, the stack unwinds and the
909-
destructors of all objects owned by that task are called.
910-
911-
The unsafe code from above can be contained behind a safe API that prevents
912-
memory leaks or use-after-free:
913-
914-
~~~~
915-
use core::libc::{calloc, free, c_void, size_t};
916-
917-
struct Blob { priv ptr: *c_void }
918-
919-
impl Blob {
920-
fn new() -> Blob {
921-
unsafe { Blob{ptr: calloc(1, int::bytes as size_t)} }
922-
}
923-
}
924-
925-
impl Drop for Blob {
926-
fn finalize(&self) {
927-
unsafe { free(self.ptr); }
928-
}
929-
}
930-
931-
fn main() {
932-
let a = Blob::new();
933-
934-
let d;
935-
936-
{
937-
let b = Blob::new();
938-
939-
let c = Blob::new();
940-
d = c; // move ownership to d
941-
942-
// b is destroyed here
943-
}
944-
945-
// d is destroyed here
946-
// a is destroyed here
882+
{
883+
// an integer allocated on the heap
884+
let y = ~10;
947885
}
886+
// the destructor frees the heap memory as soon as `y` goes out of scope
948887
~~~~
949888

950-
This pattern is common enough that Rust includes dynamically allocated memory
951-
as first-class types (`~` and `@`). Non-memory resources like files are cleaned
952-
up with custom destructors.
953-
954-
~~~~
955-
fn main() {
956-
let a = ~0;
957-
958-
let d;
959-
960-
{
961-
let b = ~0;
962-
963-
let c = ~0;
964-
d = c; // move ownership to d
965-
966-
// b is destroyed here
967-
}
968-
969-
// d is destroyed here
970-
// a is destroyed here
971-
}
972-
~~~~
889+
Rust includes syntax for heap memory allocation in the language since it's
890+
commonly used, but the same semantics can be implemented by a type with a
891+
custom destructor.
973892

974893
# Ownership
975894

@@ -984,6 +903,22 @@ and destroy the contained object when they go out of scope. A box managed by
984903
the garbage collector starts a new ownership tree, and the destructor is called
985904
when it is collected.
986905

906+
~~~~
907+
// the struct owns the objects contained in the `x` and `y` fields
908+
struct Foo { x: int, y: ~int }
909+
910+
{
911+
// `a` is the owner of the struct, and thus the owner of the struct's fields
912+
let a = Foo { x: 5, y: ~10 };
913+
}
914+
// when `a` goes out of scope, the destructor for the `~int` in the struct's
915+
// field is called
916+
917+
// `b` is mutable, and the mutability is inherited by the objects it owns
918+
let mut b = Foo { x: 5, y: ~10 };
919+
b.x = 10;
920+
~~~~
921+
987922
If an object doesn't contain garbage-collected boxes, it consists of a single
988923
ownership tree and is given the `Owned` trait which allows it to be sent
989924
between tasks. Custom destructors can only be implemented directly on types
@@ -1007,7 +942,7 @@ refer to that through a pointer.
1007942
## Owned boxes
1008943

1009944
An owned box (`~`) is a uniquely owned allocation on the heap. It inherits the
1010-
mutability and lifetime of the owner as it would if there was no box.
945+
mutability and lifetime of the owner as it would if there was no box:
1011946

1012947
~~~~
1013948
let x = 5; // immutable
@@ -1021,8 +956,8 @@ let mut y = ~5; // mutable
1021956

1022957
The purpose of an owned box is to add a layer of indirection in order to create
1023958
recursive data structures or cheaply pass around an object larger than a
1024-
pointer. Since an owned box has a unique owner, it can be used to represent any
1025-
tree data structure.
959+
pointer. Since an owned box has a unique owner, it can only be used to
960+
represent a tree data structure.
1026961

1027962
The following struct won't compile, because the lack of indirection would mean
1028963
it has an infinite size:
@@ -1092,7 +1027,6 @@ d = b; // box type is the same, okay
10921027
c = b; // error
10931028
~~~~
10941029

1095-
10961030
# Move semantics
10971031

10981032
Rust uses a shallow copy for parameter passing, assignment and returning values

0 commit comments

Comments
 (0)