diff --git a/configure b/configure index 980624f0ecc..9f3ecddefe1 100755 --- a/configure +++ b/configure @@ -508,6 +508,7 @@ do make_dir $h/test/doc-tutorial-ffi make_dir $h/test/doc-tutorial-macros make_dir $h/test/doc-tutorial-borrowed-ptr + make_dir $h/test/doc-tutorial-tasks make_dir $h/test/doc-ref done diff --git a/doc/tutorial-tasks.md b/doc/tutorial-tasks.md new file mode 100644 index 00000000000..2f540ef9ee6 --- /dev/null +++ b/doc/tutorial-tasks.md @@ -0,0 +1,173 @@ +% Tasks and communication in Rust + +Rust supports a system of lightweight tasks, similar to what is found +in Erlang or other actor systems. Rust tasks communicate via messages +and do not share data. However, it is possible to send data without +copying it by making use of [the exchange heap](#unique-boxes), which +allow the sending task to release ownership of a value, so that the +receiving task can keep on using it. + +> ***Note:*** As Rust evolves, we expect the task API to grow and +> change somewhat. The tutorial documents the API as it exists today. + +# Spawning a task + +Spawning a task is done using the various spawn functions in the +module `task`. Let's begin with the simplest one, `task::spawn()`: + +~~~~ +use task::spawn; +use io::println; + +let some_value = 22; + +do spawn { + println(~"This executes in the child task."); + println(fmt!("%d", some_value)); +} +~~~~ + +The argument to `task::spawn()` is a [unique +closure](#unique-closures) of type `fn~()`, meaning that it takes no +arguments and generates no return value. The effect of `task::spawn()` +is to fire up a child task that will execute the closure in parallel +with the creator. + +# Communication + +Now that we have spawned a child task, it would be nice if we could +communicate with it. This is done using *pipes*. Pipes are simply a +pair of endpoints, with one for sending messages and another for +receiving messages. The easiest way to create a pipe is to use +`pipes::stream`. Imagine we wish to perform two expensive +computations in parallel. We might write something like: + +~~~~ +use task::spawn; +use pipes::{stream, Port, Chan}; + +let (chan, port) = stream(); + +do spawn { + let result = some_expensive_computation(); + chan.send(result); +} + +some_other_expensive_computation(); +let result = port.recv(); + +# fn some_expensive_computation() -> int { 42 } +# fn some_other_expensive_computation() {} +~~~~ + +Let's walk through this code line-by-line. The first line creates a +stream for sending and receiving integers: + +~~~~ {.ignore} +# use pipes::stream; +let (chan, port) = stream(); +~~~~ + +This port is where we will receive the message from the child task +once it is complete. The channel will be used by the child to send a +message to the port. The next statement actually spawns the child: + +~~~~ +# use task::{spawn}; +# use comm::{Port, Chan}; +# fn some_expensive_computation() -> int { 42 } +# let port = Port(); +# let chan = port.chan(); +do spawn { + let result = some_expensive_computation(); + chan.send(result); +} +~~~~ + +This child will perform the expensive computation send the result +over the channel. (Under the hood, `chan` was captured by the +closure that forms the body of the child task. This capture is +allowed because channels are sendable.) + +Finally, the parent continues by performing +some other expensive computation and then waiting for the child's result +to arrive on the port: + +~~~~ +# use pipes::{stream, Port, Chan}; +# fn some_other_expensive_computation() {} +# let (chan, port) = stream::(); +# chan.send(0); +some_other_expensive_computation(); +let result = port.recv(); +~~~~ + +# Creating a task with a bi-directional communication path + +A very common thing to do is to spawn a child task where the parent +and child both need to exchange messages with each other. The +function `std::comm::DuplexStream()` supports this pattern. We'll +look briefly at how it is used. + +To see how `spawn_conversation()` works, we will create a child task +that receives `uint` messages, converts them to a string, and sends +the string in response. The child terminates when `0` is received. +Here is the function that implements the child task: + +~~~~ +# use std::comm::DuplexStream; +# use pipes::{Port, Chan}; +fn stringifier(channel: &DuplexStream<~str, uint>) { + let mut value: uint; + loop { + value = channel.recv(); + channel.send(uint::to_str(value, 10u)); + if value == 0u { break; } + } +} +~~~~ + +The implementation of `DuplexStream` supports both sending and +receiving. The `stringifier` function takes a `DuplexStream` that can +send strings (the first type parameter) and receive `uint` messages +(the second type parameter). The body itself simply loops, reading +from the channel and then sending its response back. The actual +response itself is simply the strified version of the received value, +`uint::to_str(value)`. + +Here is the code for the parent task: + +~~~~ +# use std::comm::DuplexStream; +# use pipes::{Port, Chan}; +# use task::spawn; +# fn stringifier(channel: &DuplexStream<~str, uint>) { +# let mut value: uint; +# loop { +# value = channel.recv(); +# channel.send(uint::to_str(value, 10u)); +# if value == 0u { break; } +# } +# } +# fn main() { + +let (from_child, to_child) = DuplexStream(); + +do spawn || { + stringifier(&to_child); +}; + +from_child.send(22u); +assert from_child.recv() == ~"22"; + +from_child.send(23u); +from_child.send(0u); + +assert from_child.recv() == ~"23"; +assert from_child.recv() == ~"0"; + +# } +~~~~ + +The parent task first calls `DuplexStream` to create a pair of bidirectional endpoints. It then uses `task::spawn` to create the child task, which captures one end of the communication channel. As a result, both parent +and child can send and receive data to and from the other. diff --git a/doc/tutorial.md b/doc/tutorial.md index 6d0ddca47be..f97fbbed196 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -7,9 +7,11 @@ This is a tutorial for the Rust programming language. It assumes the reader is familiar with the basic concepts of programming, and has programmed in one or more other languages before. It will often make -comparisons to other languages in the C family. The tutorial covers -the whole language, though not with the depth and precision of the -[language reference](rust.html). +comparisons to other languages in the C family. This tutorial covers +the fundamentals of the language, including the syntax, the type +system and memory model, and generics. +[Additional tutorials](#what-next) cover specific language features in +greater depth. ## Language overview @@ -2078,257 +2080,30 @@ This makes it possible to rebind a variable without actually mutating it, which is mostly useful for destructuring (which can rebind, but not assign). -# Tasks +# What next? -Rust supports a system of lightweight tasks, similar to what is found -in Erlang or other actor systems. Rust tasks communicate via messages -and do not share data. However, it is possible to send data without -copying it by making use of [the exchange heap](#unique-boxes), which -allow the sending task to release ownership of a value, so that the -receiving task can keep on using it. +Now that you know the essentials, check out any of the additional +tutorials on individual topics. -> ***Note:*** As Rust evolves, we expect the task API to grow and -> change somewhat. The tutorial documents the API as it exists today. +* [Borrowed pointers][borrow] +* [Tasks and communication][tasks] +* [Macros][macros] +* [The foreign function interface][ffi] -## Spawning a task +There is further documentation on the [wiki], including articles about +[unit testing] in Rust, [documenting][rustdoc] and [packaging][cargo] +Rust code, and a discussion of the [attributes] used to apply metada +to code. -Spawning a task is done using the various spawn functions in the -module `task`. Let's begin with the simplest one, `task::spawn()`: +[borrow]: tutorial-borrowed-ptr.html +[tasks]: tutorial-tasks.html +[macros]: tutorial-macros.html +[ffi]: tutorial-ffi.html -~~~~ -use task::spawn; -use io::println; +[wiki]: https://github.com/mozilla/rust/wiki/Docs +[unit testing]: https://github.com/mozilla/rust/wiki/Doc-unit-testing +[rustdoc]: https://github.com/mozilla/rust/wiki/Doc-using-rustdoc +[cargo]: https://github.com/mozilla/rust/wiki/Doc-using-cargo-to-manage-packages +[attributes]: https://github.com/mozilla/rust/wiki/Doc-attributes -let some_value = 22; - -do spawn { - println(~"This executes in the child task."); - println(fmt!("%d", some_value)); -} -~~~~ - -The argument to `task::spawn()` is a [unique -closure](#unique-closures) of type `fn~()`, meaning that it takes no -arguments and generates no return value. The effect of `task::spawn()` -is to fire up a child task that will execute the closure in parallel -with the creator. - -## Communication - -Now that we have spawned a child task, it would be nice if we could -communicate with it. This is done using *pipes*. Pipes are simply a -pair of endpoints, with one for sending messages and another for -receiving messages. The easiest way to create a pipe is to use -`pipes::stream`. Imagine we wish to perform two expensive -computations in parallel. We might write something like: - -~~~~ -use task::spawn; -use pipes::{stream, Port, Chan}; - -let (chan, port) = stream(); - -do spawn { - let result = some_expensive_computation(); - chan.send(result); -} - -some_other_expensive_computation(); -let result = port.recv(); - -# fn some_expensive_computation() -> int { 42 } -# fn some_other_expensive_computation() {} -~~~~ - -Let's walk through this code line-by-line. The first line creates a -stream for sending and receiving integers: - -~~~~ {.ignore} -# use pipes::stream; -let (chan, port) = stream(); -~~~~ - -This port is where we will receive the message from the child task -once it is complete. The channel will be used by the child to send a -message to the port. The next statement actually spawns the child: - -~~~~ -# use task::{spawn}; -# use comm::{Port, Chan}; -# fn some_expensive_computation() -> int { 42 } -# let port = Port(); -# let chan = port.chan(); -do spawn { - let result = some_expensive_computation(); - chan.send(result); -} -~~~~ - -This child will perform the expensive computation send the result -over the channel. (Under the hood, `chan` was captured by the -closure that forms the body of the child task. This capture is -allowed because channels are sendable.) - -Finally, the parent continues by performing -some other expensive computation and then waiting for the child's result -to arrive on the port: - -~~~~ -# use pipes::{stream, Port, Chan}; -# fn some_other_expensive_computation() {} -# let (chan, port) = stream::(); -# chan.send(0); -some_other_expensive_computation(); -let result = port.recv(); -~~~~ - -## Creating a task with a bi-directional communication path - -A very common thing to do is to spawn a child task where the parent -and child both need to exchange messages with each other. The -function `std::comm::DuplexStream()` supports this pattern. We'll -look briefly at how it is used. - -To see how `spawn_conversation()` works, we will create a child task -that receives `uint` messages, converts them to a string, and sends -the string in response. The child terminates when `0` is received. -Here is the function that implements the child task: - -~~~~ -# use std::comm::DuplexStream; -# use pipes::{Port, Chan}; -fn stringifier(channel: &DuplexStream<~str, uint>) { - let mut value: uint; - loop { - value = channel.recv(); - channel.send(uint::to_str(value, 10u)); - if value == 0u { break; } - } -} -~~~~ - -The implementation of `DuplexStream` supports both sending and -receiving. The `stringifier` function takes a `DuplexStream` that can -send strings (the first type parameter) and receive `uint` messages -(the second type parameter). The body itself simply loops, reading -from the channel and then sending its response back. The actual -response itself is simply the strified version of the received value, -`uint::to_str(value)`. - -Here is the code for the parent task: - -~~~~ -# use std::comm::DuplexStream; -# use pipes::{Port, Chan}; -# use task::spawn; -# fn stringifier(channel: &DuplexStream<~str, uint>) { -# let mut value: uint; -# loop { -# value = channel.recv(); -# channel.send(uint::to_str(value, 10u)); -# if value == 0u { break; } -# } -# } -# fn main() { - -let (from_child, to_child) = DuplexStream(); - -do spawn || { - stringifier(&to_child); -}; - -from_child.send(22u); -assert from_child.recv() == ~"22"; - -from_child.send(23u); -from_child.send(0u); - -assert from_child.recv() == ~"23"; -assert from_child.recv() == ~"0"; - -# } -~~~~ - -The parent task first calls `DuplexStream` to create a pair of bidirectional endpoints. It then uses `task::spawn` to create the child task, which captures one end of the communication channel. As a result, both parent -and child can send and receive data to and from the other. - -# Testing - -The Rust language has a facility for testing built into the language. -Tests can be interspersed with other code, and annotated with the -`#[test]` attribute. - -~~~~{.xfail-test} -# // FIXME: xfailed because test_twice is a #[test] function it's not -# // getting compiled -extern mod std; - -fn twice(x: int) -> int { x + x } - -#[test] -fn test_twice() { - let mut i = -100; - while i < 100 { - assert twice(i) == 2 * i; - i += 1; - } -} -~~~~ - -When you compile the program normally, the `test_twice` function will -not be included. To compile and run such tests, compile with the -`--test` flag, and then run the result: - -~~~~ {.notrust} -> rustc --test twice.rs -> ./twice -running 1 tests -test test_twice ... ok -result: ok. 1 passed; 0 failed; 0 ignored -~~~~ - -Or, if we change the file to fail, for example by replacing `x + x` -with `x + 1`: - -~~~~ {.notrust} -running 1 tests -test test_twice ... FAILED -failures: - test_twice -result: FAILED. 0 passed; 1 failed; 0 ignored -~~~~ - -You can pass a command-line argument to a program compiled with -`--test` to run only the tests whose name matches the given string. If -we had, for example, test functions `test_twice`, `test_once_1`, and -`test_once_2`, running our program with `./twice test_once` would run -the latter two, and running it with `./twice test_once_2` would run -only the last. - -To indicate that a test is supposed to fail instead of pass, you can -give it a `#[should_fail]` attribute. - -~~~~ -extern mod std; - -fn divide(a: float, b: float) -> float { - if b == 0f { fail; } - a / b -} - -#[test] -#[should_fail] -fn divide_by_zero() { divide(1f, 0f); } - -# fn main() { } -~~~~ - -To disable a test completely, add an `#[ignore]` attribute. Running a -test runner (the program compiled with `--test`) with an `--ignored` -command-line flag will cause it to also run the tests labelled as -ignored. - -A program compiled as a test runner will have the configuration flag -`test` defined, so that you can add code that won't be included in a -normal compile with the `#[cfg(test)]` attribute (for a full explanation -of attributes, see the [language reference](rust.html)). +[pound-rust]: http://chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust diff --git a/mk/docs.mk b/mk/docs.mk index 084f027b434..9b2f00923c7 100644 --- a/mk/docs.mk +++ b/mk/docs.mk @@ -113,6 +113,16 @@ doc/tutorial-borrowed-ptr.html: tutorial-borrowed-ptr.md doc/version_info.html d --include-before-body=doc/version_info.html \ --output=$@ +DOCS += doc/tutorial-tasks.html +doc/tutorial-tasks.html: tutorial-tasks.md doc/version_info.html doc/rust.css + @$(call E, pandoc: $@) + $(Q)$(CFG_NODE) $(S)doc/prep.js --highlight $< | \ + $(CFG_PANDOC) --standalone --toc \ + --section-divs --number-sections \ + --from=markdown --to=html --css=rust.css \ + --include-before-body=doc/version_info.html \ + --output=$@ + endif endif diff --git a/mk/tests.mk b/mk/tests.mk index 3b092833cad..d8457a4f497 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -179,6 +179,11 @@ doc-tutorial-borrowed-ptr-extract$(1): $$(Q)rm -f $(1)/test/doc-tutorial-borrowed-ptr/*.rs $$(Q)$$(EXTRACT_TESTS) $$(S)doc/tutorial-borrowed-ptr.md $(1)/test/doc-tutorial-borrowed-ptr +doc-tutorial-tasks-extract$(1): + @$$(call E, extract: tutorial-tasks tests) + $$(Q)rm -f $(1)/test/doc-tutorial-tasks/*.rs + $$(Q)$$(EXTRACT_TESTS) $$(S)doc/tutorial-tasks.md $(1)/test/doc-tutorial-tasks + doc-ref-extract$(1): @$$(call E, extract: ref tests) $$(Q)rm -f $(1)/test/doc-ref/*.rs @@ -229,6 +234,7 @@ check-stage$(1)-T-$(2)-H-$(3): \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-ffi \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-macros \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-borrowed-ptr \ + check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-tasks \ check-stage$(1)-T-$(2)-H-$(3)-doc-ref check-stage$(1)-T-$(2)-H-$(3)-core: \ @@ -298,6 +304,9 @@ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-macros: \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-borrowed-ptr: \ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-borrowed-ptr-dummy +check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-tasks: \ + check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-tasks-dummy + check-stage$(1)-T-$(2)-H-$(3)-doc-ref: \ check-stage$(1)-T-$(2)-H-$(3)-doc-ref-dummy @@ -485,6 +494,12 @@ DOC_TUTORIAL_BORROWED_PTR_ARGS$(1)-T-$(2)-H-$(3) := \ --build-base $(3)/test/doc-tutorial-borrowed-ptr/ \ --mode run-pass +DOC_TUTORIAL_TASKS_ARGS$(1)-T-$(2)-H-$(3) := \ + $$(CTEST_COMMON_ARGS$(1)-T-$(2)-H-$(3)) \ + --src-base $(3)/test/doc-tutorial-tasks/ \ + --build-base $(3)/test/doc-tutorial-tasks/ \ + --mode run-pass + DOC_REF_ARGS$(1)-T-$(2)-H-$(3) := \ $$(CTEST_COMMON_ARGS$(1)-T-$(2)-H-$(3)) \ --src-base $(3)/test/doc-ref/ \ @@ -611,6 +626,14 @@ check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-borrowed-ptr-dummy: \ $$(DOC_TUTORIAL_BORROWED_PTR_ARGS$(1)-T-$(2)-H-$(3)) \ --logfile tmp/check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-borrowed-ptr.log +check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-tasks-dummy: \ + $$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \ + doc-tutorial-tasks-extract$(3) + @$$(call E, run doc-tutorial-tasks: $$<) + $$(Q)$$(call CFG_RUN_CTEST,$(1),$$<,$(3)) \ + $$(DOC_TUTORIAL_TASKS_ARGS$(1)-T-$(2)-H-$(3)) \ + --logfile tmp/check-stage$(1)-T-$(2)-H-$(3)-doc-tutorial-tasks.log + check-stage$(1)-T-$(2)-H-$(3)-doc-ref-dummy: \ $$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \ doc-ref-extract$(3) @@ -748,6 +771,9 @@ check-stage$(1)-H-$(2)-doc-tutorial-macros: \ check-stage$(1)-H-$(2)-doc-tutorial-borrowed-ptr: \ $$(foreach target,$$(CFG_TARGET_TRIPLES), \ check-stage$(1)-T-$$(target)-H-$(2)-doc-tutorial-borrowed-ptr) +check-stage$(1)-H-$(2)-doc-tutorial-tasks: \ + $$(foreach target,$$(CFG_TARGET_TRIPLES), \ + check-stage$(1)-T-$$(target)-H-$(2)-doc-tutorial-tasks) check-stage$(1)-H-$(2)-doc-ref: \ $$(foreach target,$$(CFG_TARGET_TRIPLES), \ check-stage$(1)-T-$$(target)-H-$(2)-doc-ref) @@ -859,6 +885,7 @@ check-stage$(1)-doc-tutorial: check-stage$(1)-H-$$(CFG_HOST_TRIPLE)-doc-tutorial check-stage$(1)-doc-tutorial-ffi: check-stage$(1)-H-$$(CFG_HOST_TRIPLE)-doc-tutorial-ffi check-stage$(1)-doc-tutorial-macros: check-stage$(1)-H-$$(CFG_HOST_TRIPLE)-doc-tutorial-macros check-stage$(1)-doc-tutorial-borrowed-ptr: check-stage$(1)-H-$$(CFG_HOST_TRIPLE)-doc-tutorial-borrowed-ptr +check-stage$(1)-doc-tutorial-tasks: check-stage$(1)-H-$$(CFG_HOST_TRIPLE)-doc-tutorial-tasks check-stage$(1)-doc-ref: check-stage$(1)-H-$$(CFG_HOST_TRIPLE)-doc-ref endef