Customize Hooks with #[hooq::method(...)]
You can configure which method is hooked.
We will show this using an example program that prints the name field from the project’s Cargo.toml. We add the toml crate first:
cargo add toml
Basic routine to parse a provided path as TOML:
use hooq::hooq;
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let _cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
// snip
Ok(())
}
Instead of applying hooq as‑is, we will customize the hook. We also want success logging via inspect.
Use #[hooq::method(...)] to specify methods to insert. The meta variable .$so_far represents the chain built so far, letting us extend it with additional methods.
use hooq::hooq;
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let _cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
// snip
Ok(())
}
Failure still logs as before:
[mdbook-source-code/tutorial-1/src/main.rs:10:81] Os { code: 2, kind: NotFound, message: "No such file or directory" }
10> std::fs::read_to_string(path)?
|
Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Success now logs too:
Success: `std :: fs :: read_to_string(path) ?` @ Line 10: Col: 81
Success: `toml :: from_str(& std :: fs :: read_to_string(path) ?) ?` @ Line 10: Col: 83
Next we extract the package.name field with toml::Value::get. Because it returns an Option, we convert to Result using ok_or_else.
use hooq::hooq;
fn display_name(val: &toml::Value) -> Result<(), String> {
let name = val
.get("package")
.ok_or_else(|| format!("get package [Line: {}]", line!()))?
.get("name")
.ok_or_else(|| format!("get name [Line: {}]", line!()))?
.as_str()
.ok_or_else(|| format!("as_str [Line: {}]", line!()))?;
println!("name: {name}");
Ok(())
}
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name(&cargo_toml)?;
Ok(())
}
As you may guess: .ok_or_else(...) is boilerplate. hooq can reduce it:
use hooq::hooq;
#[hooq]
#[hooq::method(.ok_or_else(|| {
format!("{} [Line: {}, {}]",
stringify!($source),
$line,
$nth
)
}))]
fn display_name(val: &toml::Value) -> Result<(), String> {
let name = val.get("package")?.get("name")?.as_str()?;
println!("name: {name}");
Ok(())
}
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name(&cargo_toml)?;
Ok(())
}
We also added $nth (alias of $count) to show which numbered ? caused failure. Running with an error:
Success: `std :: fs :: read_to_string(path) ?` @ Line 26: Col: 80
Success: `toml :: from_str(& std :: fs :: read_to_string(path) ?) ?` @ Line 26: Col: 82
[mdbook-source-code/tutorial-3/src/main.rs:28:30] "val.get(\"package\") ? .get(\"name\") ? [Line: 12, 2nd ?]"
28> display_name(&cargo_toml)?
|
Error: "val.get(\"package\") ? .get(\"name\") ? [Line: 12, 2nd ?]"
The TOML had package but not name; the second ? failed as indicated.
Skipping Hooks with #[hooq::skip_all]
Suppose we add a validation: “4 is ominous (like 4041), so reject names containing 4.”
use hooq::hooq;
#[hooq]
#[hooq::method(.ok_or_else(|| {
format!("{} [Line: {}, {}]",
stringify!($source),
$line,
$nth
)
}))]
fn display_name_by_mista(val: &toml::Value) -> Result<(), String> {
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(format!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
hooq also tries to hook return Err(...); but Result lacks ok_or_else, causing a compile error2:
error[E0599]: no method named `ok_or_else` found for enum `Result<T, E>` in the current scope
--> mdbook-source-code/tutorial-4-compile-error/src/main.rs:15:9
|
15 | return Err(format!(
| ^^^^^^
|
help: there is a method `or_else` with a similar name
|
15 - return Err(format!(
15 + or_else Err(format!(
|
For more information about this error, try `rustc --explain E0599`.
error: could not compile `tutorial-4-compile-error` (bin "tutorial-4-compile-error") due to 1 previous error
We do not want a hook here. Add #[hooq::skip_all] to skip inside that scope:
use hooq::hooq;
#[hooq]
#[hooq::method(.ok_or_else(|| {
format!("{} [Line: {}, {}]",
stringify!($source),
$line,
$nth
)
}))]
fn display_name_by_mista(val: &toml::Value) -> Result<(), String> {
let name = val.get("package")?.get("name")?.as_str()?;
#[hooq::skip_all]
if name.contains("4") {
return Err(format!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
Complete program
use hooq::hooq;
#[hooq]
#[hooq::method(.ok_or_else(|| {
format!("{} [Line: {}, {}]",
stringify!($source),
$line,
$nth
)
}))]
fn display_name_by_mista(val: &toml::Value) -> Result<(), String> {
let name = val.get("package")?.get("name")?.as_str()?;
#[hooq::skip_all]
if name.contains("4") {
return Err(format!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq]
#[hooq::method(.$so_far.inspect(|_| {
println!("Success: `{}` @ Line {}: Col: {}", stringify!($source), $line, $col);
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
Compilation succeeds and names containing 4 produce an error:
Success: `std :: fs :: read_to_string(path) ?` @ Line 33: Col: 80
Success: `toml :: from_str(& std :: fs :: read_to_string(path) ?) ?` @ Line 33: Col: 82
[mdbook-source-code/tutorial-4/src/main.rs:35:39] "name `tutorial-4` contains '4'. Guido Mista disallow this."
35> display_name_by_mista(&cargo_toml)?
|
Error: "name `tutorial-4` contains '4'. Guido Mista disallow this."
Besides #[hooq::skip_all], multiple attributes exist to adjust behavior mid‑function (including attaching #[hooq::method(...)] to specific expressions). See Attributes.
Custom methods everywhere may get tedious. The next lesson introduces flavor presets.