Skip to content

Commit c06cd14

Browse files
committed
async1: Read input from files
1 parent 02a877c commit c06cd14

10 files changed

Lines changed: 106 additions & 65 deletions

File tree

dev/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ edition = "2024"
199199
publish = false
200200

201201
[dependencies]
202-
tokio = { version = "1", features = ["rt"] }
202+
tokio = { version = "1", features = ["fs", "macros", "rt", "rt-multi-thread"] }
203203

204204
[profile.release]
205205
panic = "abort"

exercises/24_async/async1.rs

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,38 @@
44
// together, they can finish the job much faster.
55
//
66
// Let's simulate this using asynchronous programming. Each person is
7-
// represented as an asynchronous task, which can be executed concurrently (i.e.
8-
// they can be doing the calculations at the same time).
9-
10-
fn main() {
11-
// Async tasks need to be executed by a "runtime", which is not provided by
12-
// Rust's standard library. Here, we use the mainstream runtime `tokio`.
13-
let rt = tokio::runtime::Builder::new_current_thread()
14-
.build()
15-
.unwrap();
16-
17-
let scores_class_a = &[83, 77, 92];
18-
let scores_class_b = &[84, 88, 96];
19-
let scores_class_c = &[71, 83, 76];
7+
// represented as an asynchronous task, which can be executed concurrently.
208

9+
// Async tasks need to be executed by a "runtime", which is not provided by
10+
// Rust's standard library. Here, we use the mainstream runtime `tokio`.
11+
// The macro `tokio::main` wraps the entire main function in a runtime.
12+
#[tokio::main]
13+
async fn main() {
2114
// TODO: Fix the compiler errors by making the spawned function async.
22-
let alice = rt.spawn(calculate_mean_score(scores_class_a));
23-
let bob = rt.spawn(calculate_mean_score(scores_class_b));
24-
let catherine = rt.spawn(calculate_mean_score(scores_class_c));
25-
26-
// Block the runtime on a task that awaits all three calculations.
27-
let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async {
28-
[
29-
// TODO: "await" all three tasks to fix the compiler error.
30-
alice, bob, catherine,
31-
]
32-
});
15+
let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt"));
16+
let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt"));
17+
let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt"));
3318

34-
assert_eq!(mean_score_a, 84);
35-
assert_eq!(mean_score_b, 89);
36-
assert_eq!(mean_score_c, 76);
19+
// TODO: Await the spawned tasks to check their results.
20+
assert_eq!(mean_score_a, 84); // alice
21+
assert_eq!(mean_score_b, 89); // bob
22+
assert_eq!(mean_score_c, 76); // catherine
3723
}
3824

39-
fn calculate_mean_score(score_list: &[usize]) -> usize {
40-
let score_sum: usize = score_list.iter().sum();
41-
score_sum / score_list.len()
25+
// TODO: Fix the compiler errors by making the spawned function async.
26+
fn calculate_mean_score(scores_file: &str) -> usize {
27+
// Read the file asynchronously
28+
let file = tokio::fs::read_to_string(scores_file).await.unwrap();
29+
30+
// Initialize the sum and the number of scores
31+
let mut sum = 0;
32+
let mut n = 0;
33+
for line in file.lines() {
34+
// Parse every line as a score
35+
let score = line.parse::<usize>().unwrap();
36+
sum += score;
37+
n += 1;
38+
}
39+
40+
sum / n
4241
}

input_files/scores_class_a.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
83
2+
77
3+
92

input_files/scores_class_b.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
84
2+
88
3+
96

input_files/scores_class_c.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
71
2+
83
3+
76

rustlings-macros/info.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,3 +1225,12 @@ the functions "tim", "carl" and "nick".
12251225
12261226
An async task can wait for another one to complete by "awaiting" it. Add
12271227
".await" after the three "task_name" variables in the "block_on" call."""
1228+
1229+
[[input_files]]
1230+
name = "scores_class_a.txt"
1231+
1232+
[[input_files]]
1233+
name = "scores_class_b.txt"
1234+
1235+
[[input_files]]
1236+
name = "scores_class_c.txt"

rustlings-macros/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ struct ExerciseInfo<'a> {
88
dir: &'a str,
99
}
1010

11+
#[derive(Deserialize)]
12+
struct InputFileInfo<'a> {
13+
name: &'a str,
14+
}
15+
1116
#[derive(Deserialize)]
1217
struct InfoFile<'a> {
1318
#[serde(borrow)]
1419
exercises: Vec<ExerciseInfo<'a>>,
20+
input_files: Vec<InputFileInfo<'a>>,
1521
}
1622

1723
#[proc_macro]
@@ -25,9 +31,8 @@ pub fn include_files(_: TokenStream) -> TokenStream {
2531
.collect(),
2632
)
2733
.expect("Failed to parse `info.toml` as UTF8");
28-
let exercises = toml::de::from_str::<InfoFile>(&info_file)
29-
.expect("Failed to parse `info.toml`")
30-
.exercises;
34+
let info = toml::de::from_str::<InfoFile>(&info_file).expect("Failed to parse `info.toml`");
35+
let exercises = &info.exercises;
3136

3237
let exercise_files = exercises
3338
.iter()
@@ -54,11 +59,18 @@ pub fn include_files(_: TokenStream) -> TokenStream {
5459
.iter()
5560
.map(|dir| format!("../exercises/{dir}/README.md"));
5661

62+
let input_file_names = info.input_files.iter().map(|f| f.name);
63+
let input_file_paths = info
64+
.input_files
65+
.iter()
66+
.map(|f| format!("../input_files/{}", f.name));
67+
5768
quote! {
5869
EmbeddedFiles {
5970
info_file: #info_file,
6071
exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*],
61-
exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*]
72+
exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*],
73+
input_files: &[#(InputFile { name: #input_file_names, content: include_str!(#input_file_paths) }),*],
6274
}
6375
}
6476
.into()

solutions/24_async/async1.rs

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,35 @@
44
// together, they can finish the job much faster.
55
//
66
// Let's simulate this using asynchronous programming. Each person is
7-
// represented as an asynchronous task, which can be executed concurrently (i.e.
8-
// they can be doing the calculations at the same time).
7+
// represented as an asynchronous task, which can be executed concurrently.
98

10-
fn main() {
11-
// Async tasks need to be executed by a "runtime", which is not provided by
12-
// Rust's standard library. Here, we use the mainstream runtime `tokio`.
13-
let rt = tokio::runtime::Builder::new_current_thread()
14-
.build()
15-
.unwrap();
9+
// Async tasks need to be executed by a "runtime", which is not provided by
10+
// Rust's standard library. Here, we use the mainstream runtime `tokio`.
11+
// The macro `tokio::main` wraps the entire main function in a runtime.
12+
#[tokio::main]
13+
async fn main() {
14+
let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt"));
15+
let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt"));
16+
let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt"));
1617

17-
let scores_class_a = &[83, 77, 92];
18-
let scores_class_b = &[84, 88, 96];
19-
let scores_class_c = &[71, 83, 76];
20-
21-
let alice = rt.spawn(calculate_mean_score(scores_class_a));
22-
let bob = rt.spawn(calculate_mean_score(scores_class_b));
23-
let catherine = rt.spawn(calculate_mean_score(scores_class_c));
18+
assert_eq!(mean_score_a.await.unwrap(), 84); // alice
19+
assert_eq!(mean_score_b.await.unwrap(), 89); // bob
20+
assert_eq!(mean_score_c.await.unwrap(), 76); // catherine
21+
}
2422

25-
// Block the runtime on a task that awaits all three calculations.
26-
let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async {
27-
[
28-
alice.await.unwrap(),
29-
bob.await.unwrap(),
30-
catherine.await.unwrap(),
31-
]
32-
});
23+
async fn calculate_mean_score(scores_file: &str) -> usize {
24+
// Read the file asynchronously
25+
let file = tokio::fs::read_to_string(scores_file).await.unwrap();
3326

34-
assert_eq!(mean_score_a, 84);
35-
assert_eq!(mean_score_b, 89);
36-
assert_eq!(mean_score_c, 76);
37-
}
27+
// Initialize the sum and the number of scores
28+
let mut sum = 0;
29+
let mut n = 0;
30+
for line in file.lines() {
31+
// Parse every line as a score
32+
let score = line.parse::<usize>().unwrap();
33+
sum += score;
34+
n += 1;
35+
}
3836

39-
async fn calculate_mean_score(score_list: &[usize]) -> usize {
40-
let score_sum: usize = score_list.iter().sum();
41-
score_sum / score_list.len()
37+
sum / n
4238
}

src/embedded.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ struct ExerciseFiles {
1919
dir_ind: usize,
2020
}
2121

22+
// Input files that may be read by exercises.
23+
pub struct InputFile {
24+
pub name: &'static str,
25+
pub content: &'static str,
26+
}
27+
2228
fn create_dir_if_not_exists(path: &str) -> Result<()> {
2329
if let Err(e) = create_dir(path)
2430
&& e.kind() != io::ErrorKind::AlreadyExists
@@ -58,6 +64,7 @@ pub struct EmbeddedFiles {
5864
pub info_file: &'static str,
5965
exercise_files: &'static [ExerciseFiles],
6066
pub exercise_dirs: &'static [ExerciseDir],
67+
pub input_files: &'static [InputFile],
6168
}
6269

6370
impl EmbeddedFiles {

src/init.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,15 @@ pub fn init() -> Result<()> {
144144
.with_context(|| format!("Failed to create the file {solution_path}"))?;
145145
}
146146

147+
// init input files
148+
create_dir("input_files").context("Failed to create the directory `input_files`")?;
149+
for input_file in EMBEDDED_FILES.input_files {
150+
fs::write(
151+
format!("input_files/{}", input_file.name),
152+
input_file.content,
153+
)?;
154+
}
155+
147156
let current_cargo_toml = include_str!("../dev-Cargo.toml");
148157
// Skip the first line (comment).
149158
let newline_ind = current_cargo_toml

0 commit comments

Comments
 (0)