Skip to content

Commit 290da6f

Browse files
committedApr 18, 2015
Remove the 30 minute intro
Fixes rust-lang#24569.
1 parent a81ce5f commit 290da6f

File tree

1 file changed

+2
-583
lines changed

1 file changed

+2
-583
lines changed
 

‎src/doc/intro.md

+2-583
Original file line numberDiff line numberDiff line change
@@ -1,586 +1,5 @@
11
% A 30-minute Introduction to Rust
22

3-
Rust is a modern systems programming language focusing on safety and speed. It
4-
accomplishes these goals by being memory safe without using garbage collection.
3+
This introduction is now deprecated. Please see [the introduction to the book][intro].
54

6-
This introduction will give you a rough idea of what Rust is like, eliding many
7-
details. It does not require prior experience with systems programming, but you
8-
may find the syntax easier if you've used a "curly brace" programming language
9-
before, like C or JavaScript. The concepts are more important than the syntax,
10-
so don't worry if you don't get every last detail: you can read [The
11-
Rust Programming Language](book/index.html) to get a more complete explanation.
12-
13-
Because this is about high-level concepts, you don't need to actually install
14-
Rust to follow along. If you'd like to anyway, check out [the
15-
homepage](http://rust-lang.org) for explanation.
16-
17-
To show off Rust, let's talk about how easy it is to get started with Rust.
18-
Then, we'll talk about Rust's most interesting feature, *ownership*, and
19-
then discuss how it makes concurrency easier to reason about. Finally,
20-
we'll talk about how Rust breaks down the perceived dichotomy between speed
21-
and safety.
22-
23-
# Tools
24-
25-
Getting started on a new Rust project is incredibly easy, thanks to Rust's
26-
package manager, [Cargo](https://crates.io/).
27-
28-
To start a new project with Cargo, use `cargo new`:
29-
30-
```{bash}
31-
$ cargo new hello_world --bin
32-
```
33-
34-
We're passing `--bin` because we're making a binary program: if we
35-
were making a library, we'd leave it off.
36-
37-
Let's check out what Cargo has generated for us:
38-
39-
```{bash}
40-
$ cd hello_world
41-
$ tree .
42-
.
43-
├── Cargo.toml
44-
└── src
45-
└── main.rs
46-
47-
1 directory, 2 files
48-
```
49-
50-
This is all we need to get started. First, let's check out `Cargo.toml`:
51-
52-
```{toml}
53-
[package]
54-
55-
name = "hello_world"
56-
version = "0.0.1"
57-
authors = ["Your Name <you@example.com>"]
58-
```
59-
60-
This is called a *manifest*, and it contains all of the metadata that Cargo
61-
needs to compile your project.
62-
63-
Here's what's in `src/main.rs`:
64-
65-
```{rust}
66-
fn main() {
67-
println!("Hello, world!");
68-
}
69-
```
70-
71-
Cargo generated a "Hello World" for us. We'll talk more about the syntax here
72-
later, but that's what Rust code looks like! Let's compile and run it:
73-
74-
```{bash}
75-
$ cargo run
76-
Compiling hello_world v0.0.1 (file:///Users/you/src/hello_world)
77-
Running `target/hello_world`
78-
Hello, world!
79-
```
80-
81-
Using an external dependency in Rust is incredibly easy. You add a line to
82-
your `Cargo.toml`:
83-
84-
```{toml}
85-
[package]
86-
87-
name = "hello_world"
88-
version = "0.0.1"
89-
authors = ["Your Name <someone@example.com>"]
90-
91-
[dependencies.semver]
92-
93-
git = "https://github.com/rust-lang/semver.git"
94-
```
95-
96-
You added the `semver` library, which parses version numbers and compares them
97-
according to the [SemVer specification](http://semver.org/).
98-
99-
Now, you can pull in that library using `extern crate` in
100-
`main.rs`.
101-
102-
```{rust,ignore}
103-
extern crate semver;
104-
105-
use semver::Version;
106-
107-
fn main() {
108-
assert!(Version::parse("1.2.3") == Ok(Version {
109-
major: 1u64,
110-
minor: 2u64,
111-
patch: 3u64,
112-
pre: vec!(),
113-
build: vec!(),
114-
}));
115-
116-
println!("Versions compared successfully!");
117-
}
118-
```
119-
120-
Again, we'll discuss the exact details of all of this syntax soon. For now,
121-
let's compile and run it:
122-
123-
```{bash}
124-
$ cargo run
125-
Updating git repository `https://github.com/rust-lang/semver.git`
126-
Compiling semver v0.0.1 (https://github.com/rust-lang/semver.git#bf739419)
127-
Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
128-
Running `target/hello_world`
129-
Versions compared successfully!
130-
```
131-
132-
Because we only specified a repository without a version, if someone else were
133-
to try out our project at a later date, when `semver` was updated, they would
134-
get a different, possibly incompatible version. To solve this problem, Cargo
135-
produces a file, `Cargo.lock`, which records the versions of any dependencies.
136-
This gives us repeatable builds.
137-
138-
There is a lot more here, and this is a whirlwind tour, but you should feel
139-
right at home if you've used tools like [Bundler](http://bundler.io/),
140-
[npm](https://www.npmjs.org/), or [pip](https://pip.pypa.io/en/latest/).
141-
There's no `Makefile`s or endless `autotools` output here. (Rust's tooling does
142-
[play nice with external libraries written in those
143-
tools](http://doc.crates.io/build-script.html), if you need to.)
144-
145-
Enough about tools, let's talk code!
146-
147-
# Ownership
148-
149-
Rust's defining feature is "memory safety without garbage collection". Let's
150-
take a moment to talk about what that means. *Memory safety* means that the
151-
programming language eliminates certain kinds of bugs, such as [buffer
152-
overflows](https://en.wikipedia.org/wiki/Buffer_overflow) and [dangling
153-
pointers](https://en.wikipedia.org/wiki/Dangling_pointer). These problems occur
154-
when you have unrestricted access to memory. As an example, here's some Ruby
155-
code:
156-
157-
```{ruby}
158-
v = []
159-
160-
v.push("Hello")
161-
162-
x = v[0]
163-
164-
v.push("world")
165-
166-
puts x
167-
```
168-
169-
We make an array, `v`, and then call `push` on it. `push` is a method which
170-
adds an element to the end of an array.
171-
172-
Next, we make a new variable, `x`, that's equal to the first element of
173-
the array. Simple, but this is where the "bug" will appear.
174-
175-
Let's keep going. We then call `push` again, pushing "world" onto the
176-
end of the array. `v` now is `["Hello", "world"]`.
177-
178-
Finally, we print `x` with the `puts` method. This prints "Hello."
179-
180-
All good? Let's go over a similar, but subtly different example, in C++:
181-
182-
```{cpp}
183-
#include<iostream>
184-
#include<vector>
185-
#include<string>
186-
187-
int main() {
188-
std::vector<std::string> v;
189-
190-
v.push_back("Hello");
191-
192-
std::string& x = v[0];
193-
194-
v.push_back("world");
195-
196-
std::cout << x;
197-
}
198-
```
199-
200-
It's a little more verbose due to the static typing, but it's almost the same
201-
thing. We make a `std::vector` of `std::string`s, we call `push_back` (same as
202-
`push`) on it, take a reference to the first element of the vector, call
203-
`push_back` again, and then print out the reference.
204-
205-
There's two big differences here: one, they're not _exactly_ the same thing,
206-
and two...
207-
208-
```{bash}
209-
$ g++ hello.cpp -Wall -Werror
210-
$ ./a.out
211-
Segmentation fault (core dumped)
212-
```
213-
214-
A crash! (Note that this is actually system-dependent. Because referring to an
215-
invalid reference is undefined behavior, the compiler can do anything,
216-
including the right thing!) Even though we compiled with flags to give us as
217-
many warnings as possible, and to treat those warnings as errors, we got no
218-
errors. When we ran the program, it crashed.
219-
220-
Why does this happen? When we append to an array, its length changes. Since
221-
its length changes, we may need to allocate more memory. In Ruby, this happens
222-
as well, we just don't think about it very often. So why does the C++ version
223-
segfault when we allocate more memory?
224-
225-
The answer is that in the C++ version, `x` is a *reference* to the memory
226-
location where the first element of the array is stored. But in Ruby, `x` is a
227-
standalone value, not connected to the underlying array at all. Let's dig into
228-
the details for a moment. Your program has access to memory, provided to it by
229-
the operating system. Each location in memory has an address. So when we make
230-
our vector, `v`, it's stored in a memory location somewhere:
231-
232-
| location | name | value |
233-
|----------|------|-------|
234-
| 0x30 | v | |
235-
236-
(Address numbers made up, and in hexadecimal. Those of you with deep C++
237-
knowledge, there are some simplifications going on here, like the lack of an
238-
allocated length for the vector. This is an introduction.)
239-
240-
When we push our first string onto the array, we allocate some memory,
241-
and `v` refers to it:
242-
243-
| location | name | value |
244-
|----------|------|----------|
245-
| 0x30 | v | 0x18 |
246-
| 0x18 | | "Hello" |
247-
248-
We then make a reference to that first element. A reference is a variable
249-
that points to a memory location, so its value is the memory location of
250-
the `"Hello"` string:
251-
252-
| location | name | value |
253-
|----------|------|----------|
254-
| 0x30 | v | 0x18 |
255-
| 0x18 | | "Hello" |
256-
| 0x14 | x | 0x18 |
257-
258-
When we push `"world"` onto the vector with `push_back`, there's no room:
259-
we only allocated one element. So, we need to allocate two elements,
260-
copy the `"Hello"` string over, and update the reference. Like this:
261-
262-
| location | name | value |
263-
|----------|------|----------|
264-
| 0x30 | v | 0x08 |
265-
| 0x18 | | GARBAGE |
266-
| 0x14 | x | 0x18 |
267-
| 0x08 | | "Hello" |
268-
| 0x04 | | "world" |
269-
270-
Note that `v` now refers to the new list, which has two elements. It's all
271-
good. But our `x` didn't get updated! It still points at the old location,
272-
which isn't valid anymore. In fact, [the documentation for `push_back` mentions
273-
this](http://en.cppreference.com/w/cpp/container/vector/push_back):
274-
275-
> If the new `size()` is greater than `capacity()` then all iterators and
276-
> references (including the past-the-end iterator) are invalidated.
277-
278-
Finding where these iterators and references are is a difficult problem, and
279-
even in this simple case, `g++` can't help us here. While the bug is obvious in
280-
this case, in real code, it can be difficult to track down the source of the
281-
error.
282-
283-
Before we talk about this solution, why didn't our Ruby code have this problem?
284-
The semantics are a little more complicated, and explaining Ruby's internals is
285-
out of the scope of a guide to Rust. But in a nutshell, Ruby's garbage
286-
collector keeps track of references, and makes sure that everything works as
287-
you might expect. This comes at an efficiency cost, and the internals are more
288-
complex. If you'd really like to dig into the details, [this
289-
article](http://patshaughnessy.net/2012/1/18/seeing-double-how-ruby-shares-string-values)
290-
can give you more information.
291-
292-
Garbage collection is a valid approach to memory safety, but Rust chooses a
293-
different path. Let's examine what the Rust version of this looks like:
294-
295-
```{rust,ignore}
296-
fn main() {
297-
let mut v = vec![];
298-
299-
v.push("Hello");
300-
301-
let x = &v[0];
302-
303-
v.push("world");
304-
305-
println!("{}", x);
306-
}
307-
```
308-
309-
This looks like a bit of both: fewer type annotations, but we do create new
310-
variables with `let`. The method name is `push`, some other stuff is different,
311-
but it's pretty close. So what happens when we compile this code? Does Rust
312-
print `"Hello"`, or does Rust crash?
313-
314-
Neither. It refuses to compile:
315-
316-
```bash
317-
$ cargo run
318-
Compiling hello_world v0.0.1 (file:///Users/you/src/hello_world)
319-
main.rs:8:5: 8:6 error: cannot borrow `v` as mutable because it is also borrowed as immutable
320-
main.rs:8 v.push("world");
321-
^
322-
main.rs:6:14: 6:15 note: previous borrow of `v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `v` until the borrow ends
323-
main.rs:6 let x = &v[0];
324-
^
325-
main.rs:11:2: 11:2 note: previous borrow ends here
326-
main.rs:1 fn main() {
327-
...
328-
main.rs:11 }
329-
^
330-
error: aborting due to previous error
331-
```
332-
333-
When we try to mutate the array by `push`ing it the second time, Rust throws
334-
an error. It says that we "cannot borrow v as mutable because it is also
335-
borrowed as immutable." What does it mean by "borrowed"?
336-
337-
In Rust, the type system encodes the notion of *ownership*. The variable `v`
338-
is an *owner* of the vector. When we make a reference to `v`, we let that
339-
variable (in this case, `x`) *borrow* it for a while. Just like if you own a
340-
book, and you lend it to me, I'm borrowing the book.
341-
342-
So, when I try to modify the vector with the second call to `push`, I need
343-
to be owning it. But `x` is borrowing it. You can't modify something that
344-
you've lent to someone. And so Rust throws an error.
345-
346-
So how do we fix this problem? Well, we can make a copy of the element:
347-
348-
349-
```{rust}
350-
fn main() {
351-
let mut v = vec![];
352-
353-
v.push("Hello");
354-
355-
let x = v[0].clone();
356-
357-
v.push("world");
358-
359-
println!("{}", x);
360-
}
361-
```
362-
363-
Note the addition of `clone()`. This creates a copy of the element, leaving
364-
the original untouched. Now, we no longer have two references to the same
365-
memory, and so the compiler is happy. Let's give that a try:
366-
367-
```{bash}
368-
$ cargo run
369-
Compiling hello_world v0.0.1 (file:///Users/you/src/hello_world)
370-
Running `target/hello_world`
371-
Hello
372-
```
373-
374-
Same result. Now, making a copy can be inefficient, so this solution may not be
375-
acceptable. There are other ways to get around this problem, but this is a toy
376-
example, and because we're in an introduction, we'll leave that for later.
377-
378-
The point is, the Rust compiler and its notion of ownership has saved us from a
379-
bug that would crash the program. We've achieved safety, at compile time,
380-
without needing to rely on a garbage collector to handle our memory.
381-
382-
# Concurrency
383-
384-
Rust's ownership model can help in other ways, as well. For example, take
385-
concurrency. Concurrency is a big topic, and an important one for any modern
386-
programming language. Let's take a look at how ownership can help you write
387-
safe concurrent programs.
388-
389-
Here's an example of a concurrent Rust program:
390-
391-
```{rust}
392-
# #![feature(scoped)]
393-
use std::thread;
394-
395-
fn main() {
396-
let guards: Vec<_> = (0..10).map(|_| {
397-
thread::scoped(|| {
398-
println!("Hello, world!");
399-
})
400-
}).collect();
401-
}
402-
```
403-
404-
This program creates ten threads, which all print `Hello, world!`. The `scoped`
405-
function takes one argument, a closure, indicated by the double bars `||`. This
406-
closure is executed in a new thread created by `scoped`. The method is called
407-
`scoped` because it returns a 'join guard', which will automatically join the
408-
child thread when it goes out of scope. Because we `collect` these guards into
409-
a `Vec<T>`, and that vector goes out of scope at the end of our program, our
410-
program will wait for every thread to finish before finishing.
411-
412-
One common form of problem in concurrent programs is a *data race*.
413-
This occurs when two different threads attempt to access the same
414-
location in memory in a non-synchronized way, where at least one of
415-
them is a write. If one thread is attempting to read, and one thread
416-
is attempting to write, you cannot be sure that your data will not be
417-
corrupted. Note the first half of that requirement: two threads that
418-
attempt to access the same location in memory. Rust's ownership model
419-
can track which pointers own which memory locations, which solves this
420-
problem.
421-
422-
Let's see an example. This Rust code will not compile:
423-
424-
```{rust,ignore}
425-
# #![feature(scoped)]
426-
use std::thread;
427-
428-
fn main() {
429-
let mut numbers = vec![1, 2, 3];
430-
431-
let guards: Vec<_> = (0..3).map(|i| {
432-
thread::scoped(move || {
433-
numbers[i] += 1;
434-
println!("numbers[{}] is {}", i, numbers[i]);
435-
})
436-
}).collect();
437-
}
438-
```
439-
440-
It gives us this error:
441-
442-
```text
443-
7:25: 10:6 error: cannot move out of captured outer variable in an `FnMut` closure
444-
7 thread::scoped(move || {
445-
8 numbers[i] += 1;
446-
9 println!("numbers[{}] is {}", i, numbers[i]);
447-
10 })
448-
error: aborting due to previous error
449-
```
450-
451-
This is a little confusing because there are two closures here: the one passed
452-
to `map`, and the one passed to `thread::scoped`. In this case, the closure for
453-
`thread::scoped` is attempting to reference `numbers`, a `Vec<i32>`. This
454-
closure is a `FnOnce` closure, as that’s what `thread::scoped` takes as an
455-
argument. `FnOnce` closures take ownership of their environment. That’s fine,
456-
but there’s one detail: because of `map`, we’re going to make three of these
457-
closures. And since all three try to take ownership of `numbers`, that would be
458-
a problem. That’s what it means by ‘cannot move out of captured outer
459-
variable’: our `thread::scoped` closure wants to take ownership, and it can’t,
460-
because the closure for `map` won’t let it.
461-
462-
What to do here? Rust has a type that helps us: `Mutex<T>`. Because the threads
463-
are scoped, it is possible to use an _immutable_ reference to `numbers` inside
464-
of the closure. However, Rust prevents us from having multiple _mutable_
465-
references to the same object, so we need a `Mutex` to be able to modify what
466-
we're sharing. A Mutex will synchronize our accesses, so that we can ensure
467-
that our mutation doesn't cause a data race.
468-
469-
Here's what using a Mutex looks like:
470-
471-
```{rust}
472-
# #![feature(scoped)]
473-
use std::thread;
474-
use std::sync::Mutex;
475-
476-
fn main() {
477-
let numbers = &Mutex::new(vec![1, 2, 3]);
478-
479-
let guards: Vec<_> = (0..3).map(|i| {
480-
thread::scoped(move || {
481-
let mut array = numbers.lock().unwrap();
482-
array[i] += 1;
483-
println!("numbers[{}] is {}", i, array[i]);
484-
})
485-
}).collect();
486-
}
487-
```
488-
489-
We first have to `use` the appropriate library, and then we wrap our vector in
490-
a `Mutex` with the call to `Mutex::new()`. Inside of the loop, the `lock()`
491-
call will return us a reference to the value inside the Mutex, and block any
492-
other calls to `lock()` until said reference goes out of scope.
493-
494-
We can compile and run this program without error, and in fact, see the
495-
non-deterministic aspect:
496-
497-
```{shell}
498-
$ cargo run
499-
Compiling hello_world v0.0.1 (file:///Users/you/src/hello_world)
500-
Running `target/hello_world`
501-
numbers[1] is 3
502-
numbers[0] is 2
503-
numbers[2] is 4
504-
$ cargo run
505-
Running `target/hello_world`
506-
numbers[2] is 4
507-
numbers[1] is 3
508-
numbers[0] is 2
509-
```
510-
511-
Each time, we can get a slightly different output because the threads are not
512-
guaranteed to run in any set order. If you get the same order every time it is
513-
because each of these threads are very small and complete too fast for their
514-
indeterminate behavior to surface.
515-
516-
The important part here is that the Rust compiler was able to use ownership to
517-
give us assurance _at compile time_ that we weren't doing something incorrect
518-
with regards to concurrency. In order to share ownership, we were forced to be
519-
explicit and use a mechanism to ensure that it would be properly handled.
520-
521-
# Safety _and_ Speed
522-
523-
Safety and speed are always presented as a continuum. At one end of the spectrum,
524-
you have maximum speed, but no safety. On the other end, you have absolute safety
525-
with no speed. Rust seeks to break out of this paradigm by introducing safety at
526-
compile time, ensuring that you haven't done anything wrong, while compiling to
527-
the same low-level code you'd expect without the safety.
528-
529-
As an example, Rust's ownership system is _entirely_ at compile time. The
530-
safety check that makes this an error about moved values:
531-
532-
```{rust,ignore}
533-
# #![feature(scoped)]
534-
use std::thread;
535-
536-
fn main() {
537-
let numbers = vec![1, 2, 3];
538-
539-
let guards: Vec<_> = (0..3).map(|i| {
540-
thread::scoped(move || {
541-
println!("{}", numbers[i]);
542-
})
543-
}).collect();
544-
}
545-
```
546-
547-
carries no runtime penalty. And while some of Rust's safety features do have
548-
a run-time cost, there's often a way to write your code in such a way that
549-
you can remove it. As an example, this is a poor way to iterate through
550-
a vector:
551-
552-
```{rust}
553-
let vec = vec![1, 2, 3];
554-
555-
for i in 0..vec.len() {
556-
println!("{}", vec[i]);
557-
}
558-
```
559-
560-
The reason is that the access of `vec[i]` does bounds checking, to ensure
561-
that we don't try to access an invalid index. However, we can remove this
562-
while retaining safety. The answer is iterators:
563-
564-
```{rust}
565-
let vec = vec![1, 2, 3];
566-
567-
for x in &vec {
568-
println!("{}", x);
569-
}
570-
```
571-
572-
This version uses an iterator that yields each element of the vector in turn.
573-
Because we have a reference to the element, rather than the whole vector itself,
574-
there's no array access bounds to check.
575-
576-
# Learning More
577-
578-
I hope that this taste of Rust has given you an idea if Rust is the right
579-
language for you. We talked about Rust's tooling, how encoding ownership into
580-
the type system helps you find bugs, how Rust can help you write correct
581-
concurrent code, and how you don't have to pay a speed cost for much of this
582-
safety.
583-
584-
To continue your Rustic education, read [The Rust Programming
585-
Language](book/index.html) for a more in-depth exploration of Rust's syntax and
586-
concepts.
5+
[intro]: book/README.html

0 commit comments

Comments
 (0)
Please sign in to comment.