Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ target/
*.pdb
exercises/clippy/Cargo.toml
exercises/clippy/Cargo.lock
exercises/async/Cargo.toml
exercises/async/Cargo.lock
rust-project.json
.idea
.vscode/*
Expand Down
17 changes: 17 additions & 0 deletions exercises/async/async1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// async1.rs
// Running a basic async function. Try to get the answer to print out.
// Note that `tokio` with full features have been added to `Cargo.toml` for you already.
//
// Execute `rustlings hint async1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
let result = complex_calculation();
println!("The answer is {result}");
}

// Don't change anything below this line.
async fn complex_calculation() -> i32 {
2 + 2
}
21 changes: 21 additions & 0 deletions info.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1171,3 +1171,24 @@ path = "exercises/conversions/as_ref_mut.rs"
mode = "test"
hint = """
Add AsRef<str> as a trait bound to the functions."""

[[exercises]]
name = "async1"
path = "exercises/async/async1.rs"
mode = "async"
hint = """
The function that we need to run is async. You might want to look at
https://rust-lang.github.io/async-book/01_getting_started/04_async_await_primer.html
for a primer on using async functions.

The println! can't print out the result of the async function as it returns a Future. Is there a way to wait for the Future to complete?

The result needs to be awaited before it can be printed. You can use the .await syntax to do this.

Using await will cause the main function to become async. What happens if we add the async keyword to the main function?

Remember that we have tokio imported. To start an async function, we need a runtime to run it.

We can use the #[tokio::main] attribute macro to start the runtime. This will allow us to run async functions in the main function.
[[exercises]]
"""
32 changes: 31 additions & 1 deletion src/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"];
const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
const CONTEXT: usize = 2;
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
const ASYNC_CARGO_TOML_PATH: &str = "./exercises/async/Cargo.toml";

// Get a temporary file name that is hopefully unique
#[inline]
Expand All @@ -34,6 +35,8 @@ pub enum Mode {
Test,
// Indicates that the exercise should be linted with clippy
Clippy,
// Indicates that the exercise should be compiled as a binary with tokio
Async,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -161,6 +164,27 @@ path = "{}.rs""#,
.args(&["--", "-D", "warnings", "-D", "clippy::float_cmp"])
.output()
}

Mode::Async => {
let cargo_toml = format!(
r#"[package]
name = "{}"
version = "0.0.1"
edition = "2021"
[[bin]]
name = "{}"
path = "{}.rs"

[dependencies]
tokio = {{ version = "1.28.1", features = ["full"] }}"#,
self.name, &temp_file()[2..], self.name
);
fs::write(ASYNC_CARGO_TOML_PATH, cargo_toml).expect("Failed to write async Cargo.toml file.");
Command::new("cargo")
.args(&["build", "--manifest-path", ASYNC_CARGO_TOML_PATH])
.args(RUSTC_COLOR_ARGS)
.output()
}
}
.expect("Failed to run 'compile' command.");

Expand All @@ -183,7 +207,13 @@ path = "{}.rs""#,
Mode::Test => "--show-output",
_ => "",
};
let cmd = Command::new(&temp_file())

let bin_path = match self.mode {
Mode::Async => format!("./exercises/async/target/debug/{}", &temp_file()[2..]),
_ => temp_file()
};

let cmd = Command::new(bin_path)
.arg(arg)
.output()
.expect("Failed to run 'run' command");
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ fn main() {
project
.get_sysroot_src()
.expect("Couldn't find toolchain path, do you have `rustc` installed?");

project.get_cargo_tokio_path();
project
.exercises_to_json()
.expect("Couldn't parse rustlings exercises files");
Expand Down
61 changes: 58 additions & 3 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,32 @@ use std::process::Command;
#[derive(Serialize, Deserialize)]
pub struct RustAnalyzerProject {
sysroot_src: String,

#[serde(skip)]
cargo_tokio: String,
pub crates: Vec<Crate>,
}

#[derive(Serialize, Deserialize)]
pub struct Crate {
root_module: String,
edition: String,
deps: Vec<String>,
deps: Vec<DepData>,
cfg: Vec<String>,
}

#[derive(Serialize, Deserialize)]
pub struct DepData {
#[serde(rename="crate")]
crate_index: i32,
name: String,
}

impl RustAnalyzerProject {
pub fn new() -> RustAnalyzerProject {
RustAnalyzerProject {
sysroot_src: String::new(),
cargo_tokio: String::new(),
crates: Vec::new(),
}
}
Expand All @@ -42,23 +53,51 @@ impl RustAnalyzerProject {
fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box<dyn Error>> {
if let Some(ext) = path.extension() {
if ext == "rs" {
self.crates.push(Crate {
let mut c = Crate {
root_module: path.display().to_string(),
edition: "2021".to_string(),
deps: Vec::new(),
// This allows rust_analyzer to work inside #[test] blocks
cfg: vec!["test".to_string()],
})
};
if path.display().to_string().starts_with("exercises/async") {
c.deps = vec!(DepData{ crate_index: 0, name: "tokio".to_string()})
}
self.crates.push(c);
}
}

Ok(())
}

fn add_tokio_to_crates(&mut self) {
self.crates.push(Crate {
root_module: self.cargo_tokio.to_string(),
edition: "2021".to_string(),
deps: Vec::new(),
// This allows rust_analyzer to work inside #[test] blocks
cfg: vec![
"feature=\"fs\"".to_string(),
"feature=\"io-util\"".to_string(),
"feature=\"io-std\"".to_string(),
"feature=\"macros\"".to_string(),
"feature=\"net\"".to_string(),
"feature=\"parking_lot\"".to_string(),
"feature=\"process\"".to_string(),
"feature=\"rt\"".to_string(),
"feature=\"rt-multi-thread\"".to_string(),
"feature=\"signal\"".to_string(),
"feature=\"sync\"".to_string(),
"feature=\"time\"".to_string(),
],
});
}

/// Parse the exercises folder for .rs files, any matches will create
/// a new `crate` in rust-project.json which allows rust-analyzer to
/// treat it like a normal binary
pub fn exercises_to_json(&mut self) -> Result<(), Box<dyn Error>> {
self.add_tokio_to_crates();
for path in glob("./exercises/**/*")? {
self.path_to_json(path?)?;
}
Expand Down Expand Up @@ -96,4 +135,20 @@ impl RustAnalyzerProject {
.to_string();
Ok(())
}


pub fn get_cargo_tokio_path(&mut self) {
let home = env::var("HOME").unwrap_or_else(|_| String::from("~/"));
self.cargo_tokio = (std::path::Path::new(&home)
.join(".cargo")
.join("registry")
.join("src")
.join("github.com-1ecc6299db9ec823")
.join("tokio-1.28.1")
.join("src")
.join("lib.rs")
.to_string_lossy())
.to_string();
}

}
1 change: 1 addition & 0 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
Mode::Test => test(exercise, verbose)?,
Mode::Compile => compile_and_run(exercise)?,
Mode::Clippy => compile_and_run(exercise)?,
Mode::Async => compile_and_run(exercise)?,
}
Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn verify<'a>(
Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints),
Mode::Compile => compile_and_run_interactively(exercise, success_hints),
Mode::Clippy => compile_only(exercise, success_hints),
Mode::Async => compile_only(exercise, success_hints),
};
if !compile_result.unwrap_or(false) {
return Err(exercise);
Expand Down Expand Up @@ -152,6 +153,7 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option<String>, suc
Mode::Compile => success!("Successfully ran {}!", exercise),
Mode::Test => success!("Successfully tested {}!", exercise),
Mode::Clippy => success!("Successfully compiled {}!", exercise),
Mode::Async => success!("Successfully compiled {}!", exercise),
}

let no_emoji = env::var("NO_EMOJI").is_ok();
Expand All @@ -164,6 +166,7 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option<String>, suc

let success_msg = match exercise.mode {
Mode::Compile => "The code is compiling!",
Mode::Async => "The code is compiling!",
Mode::Test => "The code is compiling, and the tests pass!",
Mode::Clippy => clippy_success_msg,
};
Expand Down