Skip to content
Merged
Show file tree
Hide file tree
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
19 changes: 18 additions & 1 deletion .github/workflows/mdbook-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: 'true'
cache: "true"
toolchain: stable
Comment on lines 18 to 20
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This config is actually unnecessary as these are the default values


- name: Run tests
Expand All @@ -33,3 +33,20 @@ jobs:
echo "❌ Some tests failed. Check the logs above for details."
exit 1
fi
check-workspace:
# this job incrementally replaces the skeptic tests above as we convert examples to be written
# as workspace crates rather than inline in the mdbook
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo check --workspace
- name: Check Results
if: always()
run: |
if [ ${{ steps.cargo_check.outcome }} == 'success' ]; then
echo "✅ Workspace check passed!"
else
echo "❌ Workspace check failed. Check the logs above for details."
exit 1
fi
26 changes: 20 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
[workspace]
members = ["crates/algorithms/*", "crates/web", "xtask"]

[workspace.package]
edition = "2024"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Going with the latest edition here for the new code. Thoughts / problems?

Copy link
Contributor

Choose a reason for hiding this comment

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

fingers crossed. I think its only an issue with skeptic

version = "1.1.0"
authors = ["Brian Anderson <[email protected]>", "Andrew Gauger <[email protected]>"]
license = "MIT OR Apache-2.0"
publish = false

[package]
name = "rust-cookbook"
version = "1.1.0"
authors = ["Brian Anderson <[email protected]>", "Andrew Gauger <[email protected]>"]
edition = "2018"
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
Comment on lines -6 to +16
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This format was incorrect

Copy link
Contributor

Choose a reason for hiding this comment

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

Praise: Thank you for fixing that

publish = false
build = "build.rs"

[features]
default = []
test-rand = []

[workspace.dependencies]
rand = "0.9"
rand_distr = "0.5"
Comment on lines +23 to +25
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These won't yet affect the playground, but could reasonably easily by modifying the following code to include workspace deps:

https://github.com/rust-lang/rust-playground/blob/f8d7de52a3c139a0df4fe116bbbff19c30668c99/top-crates/src/lib.rs#L134-L161


# NOTE: These dependencies add dependencies to the rust playground (play.rust-lang.org).
# Be wary removing or changing these without considering this fact.
# See https://github.com/rust-lang/rust-playground/blob/f8d7de52a3c139a0df4fe116bbbff19c30668c99/top-crates/src/lib.rs#L134-L161
Comment on lines +27 to +29
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 this as it's tempting to remove the deps from here as they are not needed (rand / rand-distr are no longer included in the generated skeptic tests).

[dependencies]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re-sorted this section correctly

ansi_term = "0.11.0"
anyhow = "1.0"
approx = "0.3"
base64 = "0.22.1"
bitflags = "1.3.2"
Expand All @@ -28,7 +45,6 @@ env_logger = "0.11.3"
flate2 = "1.0"
glob = "0.3"
image = "0.24"

lazy_static = "1.0"
log = "0.4"
log4rs = "0.8"
Expand All @@ -50,7 +66,6 @@ ring = "0.17"
rusqlite = { version = "0.32", features = ["chrono"] }
same-file = "1.0"
select = "0.6.0"

semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
Expand All @@ -59,10 +74,9 @@ sha2 = "0.10"
tar = "0.4"
tempfile = "3.14"
thiserror = "2"
anyhow = "1.0"
threadpool = "1.8"
toml = "0.8"
tokio = { version = "1", features = ["full"] }
toml = "0.8"
unicode-segmentation = "1.2.1"
url = "2.5"
walkdir = "2.5"
Expand Down
20 changes: 5 additions & 15 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,17 @@ const REMOVED_TESTS: &[&str] = &[
"./src/web/clients/api/rate-limited.md",
];

fn main() {
#[cfg(feature = "test-rand")]
{
let rand_paths = vec![
"./src/algorithms/randomness/rand.md",
"./src/algorithms/randomness/rand-range.md",
"./src/algorithms/randomness/rand-dist.md",
"./src/algorithms/randomness/rand-custom.md",
"./src/algorithms/randomness/rand-passwd.md",
"./src/algorithms/randomness/rand-choose.md",
];
skeptic::generate_doc_tests(&rand_paths[..]);
return;
}
const REMOVED_PREFIXES: &[&str] = &["./src/algorithms/randomness/"];
Copy link
Contributor Author

@joshka joshka Oct 2, 2025

Choose a reason for hiding this comment

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

Went with a prefix approach here as it makes it easy to crank down the folders, e.g once algorithms/sorting is also done, this can just include ./src/algorithms.


let paths = WalkDir::new("./src/").into_iter()
fn main() {
let paths = WalkDir::new("./src/")
.into_iter()
// convert paths to Strings
.map(|p| p.unwrap().path().to_str().unwrap().to_string())
// only compile markdown files
.filter(|p| p.ends_with(".md"))
.filter(|p| !REMOVED_TESTS.contains(&p.as_ref()))
.filter(|p| !REMOVED_PREFIXES.iter().any(|prefix| p.starts_with(prefix)))
.collect::<Vec<_>>();

skeptic::generate_doc_tests(&paths[..]);
Expand Down
11 changes: 11 additions & 0 deletions crates/algorithms/randomness/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "randomness"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true

[dependencies]
rand.workspace = true
rand_distr.workspace = true
19 changes: 19 additions & 0 deletions crates/algorithms/randomness/src/bin/choose.rs
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note these files are in src/bin/. An alternative would be to put these in tests/ and have them actually run when running tests. This would require adding #[test] to each main function. I'm ambivalent as to which option is better with a slight preference for src/bin, but if running each example to ensure that it doesn't error when run has a higher precedence than I've assumed then tests/ with #[test] might be more convenient.

Copy link
Contributor

Choose a reason for hiding this comment

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

Praise: This makes the recipes and tests synchronized and there's no extra boiler plate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is probably the main comment that I wanted feedback on. Do you want to run the code as part of the build, or is confirming that the code compiles enough?

The old rust adage of if it compiles it's correct is probably good enough for most things that would appear in the cookbook, but it could be reasonable to instead just write normal unit tests that show both the code and the expected output of things like this. I.e. instead of println!("...") in a main() function, we'd have have a test with an assertion that shows what's expected. This change would be useful for those that understand testing well, but it's a fairly significant difference to the way things are currently.

Copy link
Contributor

Choose a reason for hiding this comment

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

for web I wrote explicit tests, but they don't run on ci. There has to be a mix of things that run and things that don't we aren't going to get postgres deployed. I'd like some testing, optionally added.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use rand::Rng;

const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 30;

fn main() {
let mut rng = rand::rng();

let password: String = (0..PASSWORD_LEN)
.map(|_| {
let idx = rng.random_range(0..CHARSET.len());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed for rand 0.9 (gen_range -> random_range).

Moving away from skeptic here highlights deprecated code like this easily with standard tooling

Copy link
Contributor

Choose a reason for hiding this comment

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

Praise: isolating our dependencies makes them much more deterministic.

char::from(CHARSET[idx])
})
.collect();

println!("{password}");
}
25 changes: 25 additions & 0 deletions crates/algorithms/randomness/src/bin/custom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#![allow(dead_code)]
use rand::Rng;

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}

impl Point {
fn random<R: Rng>(rng: &mut R) -> Self {
Point {
x: rng.random(),
y: rng.random(),
}
}
}

fn main() {
let mut rng = rand::rng();
let rand_tuple = rng.random::<(i32, bool, f64)>();
let rand_point = Point::random(&mut rng);
println!("Random tuple: {:?}", rand_tuple);
println!("Random Point: {:?}", rand_point);
}
12 changes: 12 additions & 0 deletions crates/algorithms/randomness/src/bin/dist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use rand_distr::{Distribution, LogNormal, Normal};

fn main() {
let mut rng = rand::rng();
let normal = Normal::new(2.0, 3.0).expect("Failed to create normal distribution");
let log_normal = LogNormal::new(1.0, 0.5).expect("Failed to create log-normal distribution");

let v = normal.sample(&mut rng);
println!("{} is from a N(2, 9) distribution", v);
let v = log_normal.sample(&mut rng);
println!("{} is from an ln N(1, 0.25) distribution", v);
}
13 changes: 13 additions & 0 deletions crates/algorithms/randomness/src/bin/passwd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use rand::Rng;
use rand_distr::Alphanumeric;

fn main() {
const PASSWORD_LEN: usize = 30;
let mut rng = rand::rng();

let password: String = (0..PASSWORD_LEN)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
Comment on lines +8 to +10
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The example from where this was copied was not using Alphanumeric, but the text said it was. Fixed this in this change.


println!("{password}");
}
7 changes: 7 additions & 0 deletions crates/algorithms/randomness/src/bin/rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use rand::Rng;

fn main() {
let mut rng = rand::rng();
let random_number: u32 = rng.random();
println!("Random number: {random_number}");
}
7 changes: 7 additions & 0 deletions crates/algorithms/randomness/src/bin/random_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use rand::Rng;

fn main() {
let mut rng = rand::rng();
println!("Integer: {}", rng.random_range(0..10));
println!("Float: {}", rng.random_range(0.0..10.0));
}
15 changes: 15 additions & 0 deletions crates/algorithms/randomness/src/bin/uniform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use rand_distr::{Distribution, Uniform};

fn main() {
let mut rng = rand::rng();
let die =
Uniform::new_inclusive(1, 6).expect("Failed to create uniform distribution: invalid range");

loop {
let throw = die.sample(&mut rng);
println!("Roll the die: {}", throw);
if throw == 6 {
break;
}
}
}
28 changes: 5 additions & 23 deletions src/algorithms/randomness/rand-choose.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,11 @@

[![rand-badge]][rand] [![cat-os-badge]][cat-os]

Randomly generates a string of given length ASCII characters with custom
user-defined bytestring, with [`gen_range`].
Randomly generates a string of given length ASCII characters with custom user-defined bytestring,
with [`random_range`].
Comment on lines -5 to +6
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general, I've rewrapped markdown at 100 chars to make it easier to read when editing. I'd recommend choosing a value (likely 100) and adding a markdownlint config file for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd accept a lint for it, I know the feeling about wanting to wrap lines.


```rust,edition2018
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 30;
fn main() {
let mut rng = rand::rng();
let password: String = (0..PASSWORD_LEN)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
char::from(CHARSET[idx])
})
.collect();
println!("{:?}", password);
}
```rust
{{#include ../../../crates/algorithms/randomness/src/bin/choose.rs }}
```

[`gen_range`]: https://docs.rs/rand/0.9/rand/trait.Rng.html#method.gen_range
[`random_range`]: https://docs.rs/rand/latest/rand/trait.Rng.html#method.random_range
35 changes: 6 additions & 29 deletions src/algorithms/randomness/rand-custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,12 @@

[![rand-badge]][rand] [![cat-science-badge]][cat-science]

Randomly generates a tuple `(i32, bool, f64)` and variable of user defined type `Point`.
Implements the [`Distribution`] trait on type Point for [`Standard`] in order to allow random generation.
Randomly generates a tuple `(i32, bool, f64)` and variable of user defined type `Point`. Implements
the [`Distribution`] trait on type Point for [`Standard`] in order to allow random generation.

```rust,edition2018
use rand::Rng;

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}

impl Point {
fn random<R: Rng>(rng: &mut R) -> Self {
Point {
x: rng.random(),
y: rng.random(),
}
}
}

fn main() {
let mut rng = rand::rng();
let rand_tuple = rng.random::<(i32, bool, f64)>();
let rand_point = Point::random(&mut rng);
println!("Random tuple: {:?}", rand_tuple);
println!("Random Point: {:?}", rand_point);
}
```rust
{{#include ../../../crates/algorithms/randomness/src/bin/custom.rs }}
```

[Distribution]: https://docs.rs/rand/0.9/rand/distr/trait.Distribution.html
[Standard]: https://docs.rs/rand/0.9/rand/distr/struct.Standard.html
[`Distribution`]: https://docs.rs/rand/latest/rand/distr/trait.Distribution.html
[`Standard`]: https://docs.rs/rand/latest/rand/distr/struct.Standard.html
32 changes: 8 additions & 24 deletions src/algorithms/randomness/rand-dist.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,16 @@

[![rand_distr-badge]][rand_distr] [![cat-science-badge]][cat-science]

By default, random numbers in the `rand` crate have
[uniform distribution]. The [`rand_distr`] crate provides
other kinds of distributions. To use them, you instantiate
a distribution, then sample from that distribution using
[`Distribution::sample`] with help of a random-number
generator [`rand::Rng`].
By default, random numbers in the `rand` crate have [uniform distribution]. The [`rand_distr`] crate
provides other kinds of distributions. To use them, you instantiate a distribution, then sample from
that distribution using [`Distribution::sample`] with help of a random-number generator
[`rand::Rng`].

The [distributions available are documented here][rand-distributions].
An example using the [`Normal`] distribution is shown below.
The [distributions available are documented here][rand-distributions]. An example using the
[`Normal`] distribution is shown below.

```rust,edition2018
use rand::Rng;
use rand_distr::{Distribution, LogNormal, Normal};

fn main() {
let mut rng = rand::rng();
let normal = Normal::new(2.0, 3.0)
.expect("Failed to create normal distribution");
let log_normal = LogNormal::new(1.0, 0.5)
.expect("Failed to create log-normal distribution");

let v = normal.sample(&mut rng);
println!("{} is from a N(2, 9) distribution", v);
let v = log_normal.sample(&mut rng);
println!("{} is from an ln N(1, 0.25) distribution", v);
}
```rust
{{#include ../../../crates/algorithms/randomness/src/bin/dist.rs }}
```

[`Distribution::sample`]: https://docs.rs/rand/0.9/rand/distr/trait.Distribution.html#tymethod.sample
Expand Down
Loading
Loading