diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9fb9974b77c7..a5ecccc37889 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,6 +8,27 @@ the [instructions in the README].
[instructions in the README]: README.md#building
+## Writing Exercises
+
+Each segment ends with an exercise. Exercises are typically structured as an
+`exercise.rs` containing the problem and solution. This is referenced from
+`exercise.md` and `solution.md`, using `{{#include exercise.rs:anchor_name}}` to
+match ANCHOR comments in the `exercise.rs` file. Each segment also has a
+`Cargo.toml` file containing a `[[bin]]` or `[lib]` section referring to
+`exercise.rs`, and that Cargo package is referenced from the workspace the root
+`Cargo.toml`. The result is that `exercise.rs` is built and tested by
+`cargo test`.
+
+For segments on day 1, exercises should use `fn main() { .. }` and `println!`,
+with students visually verifying the correct output. On subsequent days, prefer
+tests and omit `fn main() { .. }`. However, where tests would be difficult and
+visual verification is more natural (such as in the Logger exercise), using
+`fn main { .. }` is OK.
+
+Especially for exercises without tests, consider including tests in
+`exercise.rs` that do not appear in either `exercise.md` or `solution.md`, as
+these can ensure the solution is correct.
+
## Testing
We test the course material in several ways:
diff --git a/src/borrowing/Cargo.toml b/src/borrowing/Cargo.toml
index 4d791ebba5ad..df8e7ff941fb 100644
--- a/src/borrowing/Cargo.toml
+++ b/src/borrowing/Cargo.toml
@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
-[[bin]]
+[lib]
name = "borrowing"
path = "../../third_party/rust-on-exercism/health-statistics.rs"
diff --git a/src/borrowing/exercise.md b/src/borrowing/exercise.md
index a6b9cd3a2e88..3d8a28f1f939 100644
--- a/src/borrowing/exercise.md
+++ b/src/borrowing/exercise.md
@@ -9,10 +9,7 @@ minutes: 20
Copy the code below to and fill in the missing
method:
-```rust
-// TODO: remove this when you're done with your implementation.
-#![allow(unused_variables, dead_code)]
-
+```rust,editable
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:setup}}
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:User_visit_doctor}}
@@ -20,7 +17,5 @@ method:
}
}
-{{#include ../../third_party/rust-on-exercism/health-statistics.rs:main}}
-
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:tests}}
```
diff --git a/src/closures/exercise.md b/src/closures/exercise.md
index af8049ca54a4..6b064cb7846e 100644
--- a/src/closures/exercise.md
+++ b/src/closures/exercise.md
@@ -4,7 +4,7 @@ Building on the generic logger from this morning, implement a `Filter` which
uses a closure to filter log messages, sending those which pass the filtering
predicate to an inner logger.
-```rust,compile_fail
+```rust,compile_fail,editable
{{#include exercise.rs:setup}}
// TODO: Define and implement `Filter`.
diff --git a/src/control-flow-basics/exercise.md b/src/control-flow-basics/exercise.md
index 45d0e32ab307..812d4f98fd6d 100644
--- a/src/control-flow-basics/exercise.md
+++ b/src/control-flow-basics/exercise.md
@@ -30,7 +30,5 @@ initial `n`.
todo!("Implement this")
}
-{{#include exercise.rs:tests}}
-
{{#include exercise.rs:main}}
```
diff --git a/src/control-flow-basics/exercise.rs b/src/control-flow-basics/exercise.rs
index 4cb79df34b5a..818a70dbe05d 100644
--- a/src/control-flow-basics/exercise.rs
+++ b/src/control-flow-basics/exercise.rs
@@ -25,15 +25,14 @@ fn collatz_length(mut n: i32) -> u32 {
len
}
-// ANCHOR: tests
-#[test]
-fn test_collatz_length() {
- assert_eq!(collatz_length(11), 15);
-}
-// ANCHOR_END: tests
-
// ANCHOR: main
fn main() {
- println!("Length: {}", collatz_length(11));
+ println!("Length: {}", collatz_length(11)); // should be 15
}
// ANCHOR_END: main
+// ANCHOR_END: solution
+
+#[test]
+fn test_collatz_length() {
+ assert_eq!(collatz_length(11), 15);
+}
diff --git a/src/error-handling/Cargo.toml b/src/error-handling/Cargo.toml
index 90e7f11686a4..639cfe815fa3 100644
--- a/src/error-handling/Cargo.toml
+++ b/src/error-handling/Cargo.toml
@@ -8,6 +8,6 @@ publish = false
anyhow = "*"
thiserror = "*"
-[[bin]]
+[lib]
name = "parser"
path = "exercise.rs"
diff --git a/src/error-handling/exercise.rs b/src/error-handling/exercise.rs
index 395773806141..e39b1d19cd89 100644
--- a/src/error-handling/exercise.rs
+++ b/src/error-handling/exercise.rs
@@ -88,25 +88,30 @@ fn eval(e: Expression) -> Result {
// ANCHOR_END: solution
// ANCHOR: tests
-#[test]
-fn test_error() {
- assert_eq!(
- eval(Expression::Op {
- op: Operation::Div,
- left: Box::new(Expression::Value(99)),
- right: Box::new(Expression::Value(0)),
- }),
- Err(DivideByZeroError)
- );
-}
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_error() {
+ assert_eq!(
+ eval(Expression::Op {
+ op: Operation::Div,
+ left: Box::new(Expression::Value(99)),
+ right: Box::new(Expression::Value(0)),
+ }),
+ Err(DivideByZeroError)
+ );
+ }
-fn main() {
- let expr = Expression::Op {
- op: Operation::Sub,
- left: Box::new(Expression::Value(20)),
- right: Box::new(Expression::Value(10)),
- };
- println!("expr: {expr:?}");
- println!("result: {:?}", eval(expr));
+ #[test]
+ fn test_ok() {
+ let expr = Expression::Op {
+ op: Operation::Sub,
+ left: Box::new(Expression::Value(20)),
+ right: Box::new(Expression::Value(10)),
+ };
+ assert_eq!(eval(expr), Ok(10));
+ }
}
// ANCHOR_END: tests
diff --git a/src/generics/Cargo.toml b/src/generics/Cargo.toml
index 65af53b67772..80a38c250117 100644
--- a/src/generics/Cargo.toml
+++ b/src/generics/Cargo.toml
@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
-[[bin]]
+[lib]
name = "generics"
path = "exercise.rs"
diff --git a/src/generics/exercise.md b/src/generics/exercise.md
index 801ddd54ce4c..c41490368554 100644
--- a/src/generics/exercise.md
+++ b/src/generics/exercise.md
@@ -7,12 +7,12 @@ minutes: 10
In this short exercise, you will implement a generic `min` function that
determines the minimum of two values, using the [`Ord`] trait.
-```rust,compile_fail
+```rust,editable
use std::cmp::Ordering;
-// TODO: implement the `min` function used in `main`.
+// TODO: implement the `min` function used in the tests.
-{{#include exercise.rs:main}}
+{{#include exercise.rs:tests}}
```
diff --git a/src/generics/exercise.rs b/src/generics/exercise.rs
index dc28c39eae90..33b8315b7de5 100644
--- a/src/generics/exercise.rs
+++ b/src/generics/exercise.rs
@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+#![allow(dead_code)]
// ANCHOR: solution
use std::cmp::Ordering;
@@ -22,15 +23,22 @@ fn min(l: T, r: T) -> T {
}
}
-// ANCHOR: main
-fn main() {
+// ANCHOR: tests
+#[test]
+fn integers() {
assert_eq!(min(0, 10), 0);
assert_eq!(min(500, 123), 123);
+}
+#[test]
+fn chars() {
assert_eq!(min('a', 'z'), 'a');
assert_eq!(min('7', '1'), '1');
+}
+#[test]
+fn strings() {
assert_eq!(min("hello", "goodbye"), "goodbye");
assert_eq!(min("bat", "armadillo"), "armadillo");
}
-// ANCHOR_END: main
+// ANCHOR_END: tests
diff --git a/src/iterators/Cargo.toml b/src/iterators/Cargo.toml
index c1bd937100d8..c379e76d4d3c 100644
--- a/src/iterators/Cargo.toml
+++ b/src/iterators/Cargo.toml
@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
-[[bin]]
-name = "offset-differences"
+[lib]
+name = "offset_differences"
path = "exercise.rs"
diff --git a/src/iterators/exercise.md b/src/iterators/exercise.md
index 9594f4f73d3d..b4ba5c03f221 100644
--- a/src/iterators/exercise.md
+++ b/src/iterators/exercise.md
@@ -11,7 +11,7 @@ Copy the following code to and make the tests
pass. Use an iterator expression and `collect` the result to construct the
return value.
-```rust
+```rust,editable
{{#include exercise.rs:offset_differences}}
todo!()
}
diff --git a/src/iterators/exercise.rs b/src/iterators/exercise.rs
index 63c6a9b63b76..7e4ff829821c 100644
--- a/src/iterators/exercise.rs
+++ b/src/iterators/exercise.rs
@@ -51,5 +51,3 @@ fn test_degenerate_cases() {
assert_eq!(offset_differences(1, empty), vec![]);
}
// ANCHOR_END: unit-tests
-
-fn main() {}
diff --git a/src/lifetimes/Cargo.toml b/src/lifetimes/Cargo.toml
index 323eea3e69ee..9f9921768ae0 100644
--- a/src/lifetimes/Cargo.toml
+++ b/src/lifetimes/Cargo.toml
@@ -7,6 +7,6 @@ publish = false
[dependencies]
thiserror = "*"
-[[bin]]
+[lib]
name = "protobuf"
path = "exercise.rs"
diff --git a/src/lifetimes/exercise.md b/src/lifetimes/exercise.md
index dcc18394a95a..acd8999cf2af 100644
--- a/src/lifetimes/exercise.md
+++ b/src/lifetimes/exercise.md
@@ -83,7 +83,7 @@ What remains for you is to implement the `parse_field` function and the
// TODO: Implement ProtoMessage for Person and PhoneNumber.
-{{#include exercise.rs:main }}
+{{#include exercise.rs:tests }}
```
diff --git a/src/lifetimes/exercise.rs b/src/lifetimes/exercise.rs
index 8973a1435d4c..ace81e25abb6 100644
--- a/src/lifetimes/exercise.rs
+++ b/src/lifetimes/exercise.rs
@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+#![allow(dead_code)]
// ANCHOR: solution
// ANCHOR: preliminaries
@@ -193,21 +194,31 @@ impl<'a> ProtoMessage<'a> for PhoneNumber<'a> {
}
}
-// ANCHOR: main
-fn main() {
+// ANCHOR: tests
+#[test]
+fn test_id() {
let person_id: Person = parse_message(&[0x10, 0x2a]);
assert_eq!(person_id, Person { name: "", id: 42, phone: vec![] });
+}
+#[test]
+fn test_name() {
let person_name: Person = parse_message(&[
0x0a, 0x0e, 0x62, 0x65, 0x61, 0x75, 0x74, 0x69, 0x66, 0x75, 0x6c, 0x20,
0x6e, 0x61, 0x6d, 0x65,
]);
assert_eq!(person_name, Person { name: "beautiful name", id: 0, phone: vec![] });
+}
+#[test]
+fn test_just_person() {
let person_name_id: Person =
parse_message(&[0x0a, 0x04, 0x45, 0x76, 0x61, 0x6e, 0x10, 0x16]);
assert_eq!(person_name_id, Person { name: "Evan", id: 22, phone: vec![] });
+}
+#[test]
+fn test_phone() {
let phone: Person = parse_message(&[
0x0a, 0x00, 0x10, 0x00, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x33,
0x34, 0x2d, 0x37, 0x37, 0x37, 0x2d, 0x39, 0x30, 0x39, 0x30, 0x12, 0x04,
@@ -221,8 +232,11 @@ fn main() {
phone: vec![PhoneNumber { number: "+1234-777-9090", type_: "home" },],
}
);
+}
- // Put that all together into a single parse.
+// Put that all together into a single parse.
+#[test]
+fn test_full_person() {
let person: Person = parse_message(&[
0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a,
0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35,
@@ -243,4 +257,4 @@ fn main() {
}
);
}
-// ANCHOR_END: main
+// ANCHOR_END: tests
diff --git a/src/methods-and-traits/exercise.md b/src/methods-and-traits/exercise.md
index 56a14b5eb36f..018b89f95c91 100644
--- a/src/methods-and-traits/exercise.md
+++ b/src/methods-and-traits/exercise.md
@@ -18,7 +18,7 @@ implementing that same trait, adding behavior in the process. In the "Generics"
segment this afternoon, we will see how to make the wrapper generic over the
wrapped type.
-```rust,compile_fail
+```rust,compile_fail,editable
{{#include exercise.rs:setup}}
// TODO: Implement the `Logger` trait for `VerbosityFilter`.
diff --git a/src/modules/exercise.md b/src/modules/exercise.md
index 5a5a7b5fcccf..b5acd9761898 100644
--- a/src/modules/exercise.md
+++ b/src/modules/exercise.md
@@ -29,7 +29,7 @@ files in the `src` directory.
Here's the single-module implementation of the GUI library:
-```rust
+```rust,editable
{{#include exercise.rs:single-module}}
```
diff --git a/src/pattern-matching/Cargo.toml b/src/pattern-matching/Cargo.toml
index dc619b2ecb61..35fb9d8b07b9 100644
--- a/src/pattern-matching/Cargo.toml
+++ b/src/pattern-matching/Cargo.toml
@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
-[[bin]]
+[lib]
name = "eval"
path = "exercise.rs"
diff --git a/src/pattern-matching/exercise.md b/src/pattern-matching/exercise.md
index bbe303474b67..6dae8d63c6f3 100644
--- a/src/pattern-matching/exercise.md
+++ b/src/pattern-matching/exercise.md
@@ -46,7 +46,7 @@ evaluate to `85`. We represent this as a much bigger tree:
In code, we will represent the tree with two types:
-```rust,editable
+```rust
{{#include exercise.rs:Operation}}
{{#include exercise.rs:Expression}}
diff --git a/src/pattern-matching/exercise.rs b/src/pattern-matching/exercise.rs
index 273a740f7131..bf590a297083 100644
--- a/src/pattern-matching/exercise.rs
+++ b/src/pattern-matching/exercise.rs
@@ -127,13 +127,3 @@ fn test_zeros() {
);
}
// ANCHOR_END: tests
-
-fn main() {
- let expr = Expression::Op {
- op: Operation::Div,
- left: Box::new(Expression::Value(10)),
- right: Box::new(Expression::Value(2)),
- };
- println!("expr: {expr:?}");
- println!("result: {:?}", eval(expr));
-}
diff --git a/src/references/exercise.md b/src/references/exercise.md
index 3b41f217c16b..9500ef62bc11 100644
--- a/src/references/exercise.md
+++ b/src/references/exercise.md
@@ -7,7 +7,7 @@ minutes: 20
We will create a few utility functions for 3-dimensional geometry, representing
a point as `[f64;3]`. It is up to you to determine the function signatures.
-```rust,compile_fail
+```rust,compile_fail,editable
// Calculate the magnitude of a vector by summing the squares of its coordinates
// and taking the square root. Use the `sqrt()` method to calculate the square
// root, like `v.sqrt()`.
diff --git a/src/smart-pointers/Cargo.toml b/src/smart-pointers/Cargo.toml
index f183a4cf176b..c5262152af91 100644
--- a/src/smart-pointers/Cargo.toml
+++ b/src/smart-pointers/Cargo.toml
@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
-[[bin]]
-name = "binary-tree"
+[lib]
+name = "binary_tree"
path = "exercise.rs"
diff --git a/src/smart-pointers/exercise.md b/src/smart-pointers/exercise.md
index 6eb2b01ab06d..35060f82261f 100644
--- a/src/smart-pointers/exercise.md
+++ b/src/smart-pointers/exercise.md
@@ -14,7 +14,7 @@ Implement the following types, so that the given tests pass.
Extra Credit: implement an iterator over a binary tree that returns the values
in order.
-```rust,editable,ignore
+```rust,compile_fail,editable
{{#include exercise.rs:types}}
// Implement `new`, `insert`, `len`, and `has` for `Subtree`.
diff --git a/src/smart-pointers/exercise.rs b/src/smart-pointers/exercise.rs
index bd060e79ce1c..be92422f8d2a 100644
--- a/src/smart-pointers/exercise.rs
+++ b/src/smart-pointers/exercise.rs
@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+#![allow(dead_code)]
// ANCHOR: solution
use std::cmp::Ordering;
@@ -96,14 +97,6 @@ impl Node {
}
}
-fn main() {
- let mut tree = BinaryTree::new();
- tree.insert("foo");
- assert_eq!(tree.len(), 1);
- tree.insert("bar");
- assert!(tree.has(&"foo"));
-}
-
// ANCHOR: tests
#[cfg(test)]
mod tests {
diff --git a/src/std-traits/Cargo.toml b/src/std-traits/Cargo.toml
index e00c67b4b925..bc9dec7f5271 100644
--- a/src/std-traits/Cargo.toml
+++ b/src/std-traits/Cargo.toml
@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
-[[bin]]
-name = "std-traits"
+[lib]
+name = "std_traits"
path = "exercise.rs"
diff --git a/src/std-traits/exercise.md b/src/std-traits/exercise.md
index 400ad45b364b..dcc484f9a3ae 100644
--- a/src/std-traits/exercise.md
+++ b/src/std-traits/exercise.md
@@ -9,12 +9,12 @@ In this example, you will implement the classic
playground, and implement the missing bits. Only rotate ASCII alphabetic
characters, to ensure the result is still valid UTF-8.
-```rust,compile_fail
+```rust,editable
{{#include exercise.rs:head }}
// Implement the `Read` trait for `RotDecoder`.
-{{#include exercise.rs:main }}
+{{#include exercise.rs:tests }}
```
What happens if you chain two `RotDecoder` instances together, each rotating by
diff --git a/src/std-traits/exercise.rs b/src/std-traits/exercise.rs
index 961b261e42ec..a569485bb787 100644
--- a/src/std-traits/exercise.rs
+++ b/src/std-traits/exercise.rs
@@ -36,15 +36,7 @@ impl Read for RotDecoder {
}
}
-// ANCHOR: main
-fn main() {
- let mut rot =
- RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 };
- let mut result = String::new();
- rot.read_to_string(&mut result).unwrap();
- println!("{}", result);
-}
-
+// ANCHOR: tests
#[cfg(test)]
mod test {
use super::*;
@@ -72,4 +64,4 @@ mod test {
}
}
}
-// ANCHOR_END: main
+// ANCHOR_END: tests
diff --git a/src/testing/Cargo.toml b/src/testing/Cargo.toml
index a4eaf5fc0adb..c6abcf21018a 100644
--- a/src/testing/Cargo.toml
+++ b/src/testing/Cargo.toml
@@ -7,6 +7,6 @@ publish = false
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(never)'] }
-[[bin]]
+[lib]
name = "luhn"
path = "exercise.rs"
diff --git a/src/testing/exercise.md b/src/testing/exercise.md
index b42c9f5b29b8..c255c770271d 100644
--- a/src/testing/exercise.md
+++ b/src/testing/exercise.md
@@ -27,7 +27,7 @@ correctly.
Copy the code below to and write additional tests
to uncover bugs in the provided implementation, fixing any bugs you find.
-```rust
+```rust,editable
{{#include exercise.rs:luhn}}
{{#include exercise.rs:unit-tests}}
diff --git a/src/testing/exercise.rs b/src/testing/exercise.rs
index 8ba9c97fd979..34872e618f7f 100644
--- a/src/testing/exercise.rs
+++ b/src/testing/exercise.rs
@@ -11,8 +11,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+#![allow(dead_code)]
-// ANCHOR: solution
// This is the buggy version that appears in the problem.
#[cfg(never)]
// ANCHOR: luhn
@@ -40,6 +40,7 @@ pub fn luhn(cc_number: &str) -> bool {
// ANCHOR_END: luhn
// This is the solution and passes all of the tests below.
+// ANCHOR: solution
pub fn luhn(cc_number: &str) -> bool {
let mut sum = 0;
let mut double = false;
@@ -69,14 +70,6 @@ pub fn luhn(cc_number: &str) -> bool {
digits >= 2 && sum % 10 == 0
}
-fn main() {
- let cc_number = "1234 5678 1234 5670";
- println!(
- "Is {cc_number} a valid credit card number? {}",
- if luhn(cc_number) { "yes" } else { "no" }
- );
-}
-
// ANCHOR: unit-tests
#[cfg(test)]
mod test {
diff --git a/src/testing/unit-tests.md b/src/testing/unit-tests.md
index d9681b499b3d..debf1e5ef858 100644
--- a/src/testing/unit-tests.md
+++ b/src/testing/unit-tests.md
@@ -8,7 +8,7 @@ Rust and Cargo come with a simple unit test framework. Tests are marked with
`#[test]`. Unit tests are often put in a nested `tests` module, using
`#[cfg(test)]` to conditionally compile them only when building tests.
-```rust,editable,ignore
+```rust,editable
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(idx) => &text[..idx],
@@ -39,9 +39,3 @@ mod tests {
- This lets you unit test private helpers.
- The `#[cfg(test)]` attribute is only active when you run `cargo test`.
-
-
-
-Run the tests in the playground in order to show their results.
-
-
diff --git a/src/tuples-and-arrays/exercise.md b/src/tuples-and-arrays/exercise.md
index a6af0b97c486..f519c2b826ce 100644
--- a/src/tuples-and-arrays/exercise.md
+++ b/src/tuples-and-arrays/exercise.md
@@ -26,15 +26,10 @@ transpose a matrix (turn rows into columns):
Copy the code below to and implement the function.
This function only operates on 3x3 matrices.
-```rust,should_panic
-// TODO: remove this when you're done with your implementation.
-#![allow(unused_variables, dead_code)]
-
+```rust,should_panic,editable
{{#include exercise.rs:transpose}}
todo!()
}
-{{#include exercise.rs:tests}}
-
{{#include exercise.rs:main}}
```
diff --git a/src/tuples-and-arrays/exercise.rs b/src/tuples-and-arrays/exercise.rs
index 43291f5fe596..4b90449bfb10 100644
--- a/src/tuples-and-arrays/exercise.rs
+++ b/src/tuples-and-arrays/exercise.rs
@@ -25,7 +25,23 @@ fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
result
}
-// ANCHOR: tests
+// ANCHOR: main
+fn main() {
+ let matrix = [
+ [101, 102, 103], // <-- the comment makes rustfmt add a newline
+ [201, 202, 203],
+ [301, 302, 303],
+ ];
+
+ println!("matrix: {:#?}", matrix);
+ let transposed = transpose(matrix);
+ println!("transposed: {:#?}", transposed);
+}
+// ANCHOR_END: main
+// ANCHOR_END: solution
+
+// This test does not appear in the exercise, as this is very early in the course, but it verifies
+// that the solution is correct.
#[test]
fn test_transpose() {
let matrix = [
@@ -43,18 +59,3 @@ fn test_transpose() {
]
);
}
-// ANCHOR_END: tests
-
-// ANCHOR: main
-fn main() {
- let matrix = [
- [101, 102, 103], // <-- the comment makes rustfmt add a newline
- [201, 202, 203],
- [301, 302, 303],
- ];
-
- println!("matrix: {:#?}", matrix);
- let transposed = transpose(matrix);
- println!("transposed: {:#?}", transposed);
-}
-// ANCHOR_END: main
diff --git a/src/unsafe-rust/exercise.md b/src/unsafe-rust/exercise.md
index 4fd5b1af5faf..208ec97f732d 100644
--- a/src/unsafe-rust/exercise.md
+++ b/src/unsafe-rust/exercise.md
@@ -51,7 +51,7 @@ The [Nomicon] also has a very useful chapter about FFI.
Copy the code below to and fill in the missing
functions and methods:
-```rust,should_panic
+```rust,should_panic,editable
// TODO: remove this when you're done with your implementation.
#![allow(unused_imports, unused_variables, dead_code)]
diff --git a/theme/book.js b/theme/book.js
index 69a0fc67a337..fb4f7a939156 100644
--- a/theme/book.js
+++ b/theme/book.js
@@ -148,6 +148,11 @@ function playground_text(playground, hidden = true) {
crateType: "bin",
};
+ // If the code block has no `main` but does have tests, run those.
+ if (text.indexOf("fn main") === -1 && text.indexOf("#[test]") !== -1) {
+ params.tests = true;
+ }
+
if (text.indexOf("#![feature") !== -1) {
params.version = "nightly";
}
diff --git a/third_party/rust-on-exercism/health-statistics.rs b/third_party/rust-on-exercism/health-statistics.rs
index a9ed8cdb10c8..c91ed2479afd 100644
--- a/third_party/rust-on-exercism/health-statistics.rs
+++ b/third_party/rust-on-exercism/health-statistics.rs
@@ -50,13 +50,6 @@ impl User {
}
}
-// ANCHOR: main
-fn main() {
- let bob = User::new(String::from("Bob"), 32, 155.2);
- println!("I'm {} and my age is {}", bob.name, bob.age);
-}
-// ANCHOR_END: main
-
// ANCHOR: tests
#[test]
fn test_visit() {