CLI
- Rust course
- Intermediate
- Lesson 111/115
- 6 min
Build list and get subcommands with clap.
CLI
In the last step we connect the queries to the command line. In a real project with clap subcommands:
#[derive(Subcommand)]
enum Komut {
Liste,
Etiket { ad: String },
Yazar { ad: String },
}In the exercise we build the same structure with slice patterns. Each command turns into a filter, so the listing code stays in one place:
let suzgec: Box<dyn Fn(&Yazi) -> bool> = match args {
["liste"] => Box::new(|_| true),
["etiket", e] => Box::new(move |y| y.etiketler.iter().any(|x| x == e)),
["yazar", a] => Box::new(move |y| y.yazar == *a),
[k, ..] => return Err(format!("bilinmeyen komut: {k}")),
[] => return Err(String::from("komut gerekli")),
};The runner
Once the filter is ready, listing is the same for every command. Command handling returns Result<Vec<String>, String>; main only prints:
fn calistir(v: &[Yazi], args: &[&str]) -> Result<Vec<String>, String> {
let suzgec = /* the match above */;
Ok(v.iter()
.filter(|y| y.yayinda && suzgec(y))
.map(|y| format!("{}. {}", y.id, y.konu))
.collect())
}Practice Blog CLI
With the veri_seti() data, write the calistir function above. Run the commands ["etiket", "web"], ["yazar", "ece"] and ["sil"] in order; print the result lines, or hata: MESSAGE on error.
Starter code
#[derive(Debug, Clone)]
struct Yazi {
id: u32,
konu: String,
yazar: String,
etiketler: Vec<String>,
yayinda: bool,
}
fn veri_seti() -> Vec<Yazi> {
let y = |id, konu: &str, yazar: &str, et: &[&str], yayinda| Yazi {
id,
konu: konu.to_string(),
yazar: yazar.to_string(),
etiketler: et.iter().map(|e| e.to_string()).collect(),
yayinda,
};
vec![
y(1, "Rust ile başlangıç", "ece", &["rust", "giriş"], true),
y(2, "Sahiplik", "can", &["rust"], false),
y(3, "Web sunucusu", "ece", &["rust", "web"], true),
y(4, "Python notları", "ali", &["python"], true),
]
}
fn main() {
// buraya yaz
}Sample solution
#[derive(Debug, Clone)]
struct Yazi {
id: u32,
konu: String,
yazar: String,
etiketler: Vec<String>,
yayinda: bool,
}
fn veri_seti() -> Vec<Yazi> {
let y = |id, konu: &str, yazar: &str, et: &[&str], yayinda| Yazi {
id,
konu: konu.to_string(),
yazar: yazar.to_string(),
etiketler: et.iter().map(|e| e.to_string()).collect(),
yayinda,
};
vec![
y(1, "Rust ile başlangıç", "ece", &["rust", "giriş"], true),
y(2, "Sahiplik", "can", &["rust"], false),
y(3, "Web sunucusu", "ece", &["rust", "web"], true),
y(4, "Python notları", "ali", &["python"], true),
]
}
fn calistir(v: &[Yazi], args: &[&str]) -> Result<Vec<String>, String> {
let suzgec: Box<dyn Fn(&Yazi) -> bool> = match args {
["liste"] => Box::new(|_| true),
["etiket", e] => {
let e = e.to_string();
Box::new(move |y| y.etiketler.iter().any(|x| *x == e))
}
["yazar", a] => {
let a = a.to_string();
Box::new(move |y| y.yazar == a)
}
[k, ..] => return Err(format!("bilinmeyen komut: {k}")),
[] => return Err(String::from("komut gerekli")),
};
Ok(v.iter()
.filter(|y| y.yayinda && suzgec(y))
.map(|y| format!("{}. {}", y.id, y.konu))
.collect())
}
fn main() {
let yazilar = veri_seti();
let komutlar: [&[&str]; 3] = [&["etiket", "web"], &["yazar", "ece"], &["sil"]];
for k in komutlar {
match calistir(&yazilar, k) {
Ok(sonuclar) => {
for s in sonuclar {
println!("{}", s);
}
}
Err(e) => println!("hata: {}", e),
}
}
}Expected output
3. Web sunucusu 1. Rust ile başlangıç 3. Web sunucusu hata: bilinmeyen komut: sil
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 Why do we keep the filters in a <code>Box<dyn Fn></code>?
- Each match arm produces a closure of a different type; a common type is needed Correct answer
- It's faster
- Closures can't be called without a Box
- It isn't needed
Explanation: Every closure is a unique type; all arms of a match expression must return the same type.
2 Common filter type
let suzgec: Box<dyn ____(&Yazi) -> bool> = Box::new(|_| true);
let suzgec: Box<dyn Fn(&Yazi) -> bool> = Box::new(|_| true);
Explanation: A closure called many times that only reads is Fn.
3 Command and result
| Term | Meaning |
|---|---|
etiket web |
3. Web sunucusu |
yazar ali |
4. Python notları |
liste |
1, 3, 4 |
sil |
error: unknown command |