Clean way of taking first argument without modifiying args - rust

This program should take arguments like so: <command> n1 n2 n3... e.g mode 1 2 3 3 4. I want to store the <command> argument string and pass the rest of the arguments into a constructor for parsing.
fn main() {
let args = args();
let command = args.skip(1).next().unwrap_or_else(|| {
eprintln!("Command not found; expected one of \"mode\", \"mean\", \"median\" \"range\"");
process::exit(1);
});
let set = DataSet::from_args(args).unwrap_or_else(|err| {
eprintln!("error parsing values: {}", err);
process::exit(1);
});
match command.to_lowercase().trim() {
"mode" => println!("{:?}", set.mode()),
_ => eprintln!("Command not recognised: \"{}\"", command),
}
}
This code doesn't work because args.skip(1).next()... consumes args by adapting it into a Skip, so when I try to call DataSet::from_args(args) It is invalid because it was moved into skip.
What is the best way to achieve this? I've heard the itertools library could provide an nth method for Args but I'd prefer to do this without.

First, std has nth() method: you can use nth(1) instead of skip(1).next().
Second, Iterator is implemented for &mut Iterator. So you can just call skip() on &mut args. There is even a method for that: by_ref():
let command = args.by_ref().skip(1).next().unwrap_or_else(|| {
eprintln!("Command not found; expected one of \"mode\", \"mean\", \"median\" \"range\"");
process::exit(1);
});

Related

error handling when unwrapping several try_into calls

I have a case where I need to parse some different values out from a vector.
I made a function for it, that returns a option, which either should give a option or a None, depending on whether the unwrapping succeeds.
Currently it looks like this:
fn extract_edhoc_message(msg : Vec<u8>)-> Option<EdhocMessage>{
let mtype = msg[0];
let fcnt = msg[1..3].try_into().unwrap();
let devaddr = msg[3..7].try_into().unwrap();
let msg = msg[7..].try_into().unwrap();
Some(EdhocMessage {
m_type: mtype,
fcntup: fcnt,
devaddr: devaddr,
edhoc_msg: msg,
})
}
But, I would like to be able to return a None, if any of the unwrap calls fail.
I can do that by pattern matching on each of them, and then explicitly return a None, if anything fails, but that would a lot of repeated code.
Is there any way to say something like:
"if any of these unwraps fail, return a None?"
This is exactly what ? does. It's even shorter than the .unwrap() version:
fn extract_error_message(msg: Vec<u8>) -> Option<EdhocMessage> {
let m_type = msg[0];
let fcntup = msg[1..3].try_into().ok()?;
let devaddr = msg[3..7].try_into().ok()?;
let edhoc_msg = msg[7..].try_into().ok()?;
Some(EdhocMessage {
m_type,
fcntup,
devaddr,
edhoc_msg
})
}
See this relevant part of the Rust Book.

Is there a way to make a macro replace things in strings?

This macro should be able to replace entries in a string via an argument. For example, this would work:
let string = "Hello, world!";
replace_macro!(string, "world", "Rust"); // Hello, Rust!
I'm not sure how to do this, as all my previous attempts of just writing a regular function and calling that don't work inside macros. If possible, I'd like to be using macro_rules as opposed to a proc macro.
It is not possible. Macros cannot inspect and/or change the value of variables.
It is possible if the literal is embedded in the call (replace_macro!("Hello, world!", "world", "Rust");) but requires a proc-macro: macro_rules! macros cannot inspect and/or change literals.
It's a rather simple with a proc macro:
use quote::ToTokens;
use syn::parse::Parser;
use syn::spanned::Spanned;
type Args = syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>;
#[proc_macro]
pub fn replace_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input_span = input.span();
let args = match Args::parse_terminated.parse(input) {
Ok(args) => Vec::from_iter(args),
Err(err) => return err.into_compile_error().into(),
};
let (original, text, replacement) = match args.as_slice() {
[original, text, replacement] => (original.value(), text.value(), replacement.value()),
_ => {
return syn::Error::new(
input_span,
r#"expected `"<original>", "<text>", "<replacement>"`"#,
)
.into_compile_error()
.into()
}
};
original
.replace(&text, &replacement)
.into_token_stream()
.into()
}
It parses a list of three string literals, punctated by commas, then calls str::replace() to do the real work.

Why can't this be done with if?

I'm trying to handle errors received from an async call:
let res: Result<TcpStream, Box<dyn std::error::Error>> = session.runtime().borrow_mut().block_on(async {
let fut = TcpStream::connect(session.configuration().socket()).await?;
Ok(fut)
});
I tried to do it the old school way with an if but the compiler didn't like it:
if res.is_err() {
return Err(res);
}
After some googling I came across this:
let mut stream = match res {
Ok(res) => res,
Err(res) => return Err(res),
};
which feels very much the same but with Rusts' equivalent of a switch statement. Why can't I use the if?
if res.is_err() { return res } should work. Result is an enum with two variants: Ok which by convention holds a "successful" result, and Err which holds error information. As John pointed out, wrapping the existing Result (which happens to hold an Err) in another Err result doesn't make sense - or, more precisely, doesn't match the return type of the function.
When you use match, you unpack the result into its constituent values, and then in the error case re-pack it into a new result. Note that instead of the match statement use can use the ? operator, which would compress the declaration to just:
let mut stream = res?;

Idiomatic rust way to properly parse Clap ArgMatches

I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).
Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file.
Now I don't know how to do this in an idiomatic way.
fn main() {
let matches = App::new("findx")
.version(crate_version!())
.author(crate_authors!())
.about("find + directory operations utility")
.arg(
Arg::with_name("paths")
...
)
.arg(
Arg::with_name("patterns")
...
)
.arg(
Arg::with_name("operation")
...
)
.get_matches();
let paths;
let patterns;
let operation;
if matches.is_present("patterns") && matches.is_present("operation") {
patterns = matches.values_of("patterns").unwrap().collect();
paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
operation = match matches.value_of("operation").unwrap() { // I dont like this
"Append" => Operation::Append,
"Prepend" => Operation::Prepend,
"Rename" => Operation::Rename,
_ => {
print!("Operation unsupported");
process::exit(1);
}
};
}else if Path::new("findx.yml").is_file(){
//TODO: try load from config file
}else{
eprintln!("Command line parameters or findx.yml file must be provided");
process::exit(1);
}
if let Err(e) = findx::run(Config {
paths: paths,
patterns: patterns,
operation: operation,
}) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.
Can someone help me with this, or point me to the right direction?
Best Regards
Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.
There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.
From the clap documentation
//sets value of arg "other" to "default" if value of "--opt" is "special"
let m = App::new("prog")
.arg(Arg::with_name("opt")
.takes_value(true)
.long("opt"))
.arg(Arg::with_name("other")
.long("other")
.default_value_if("opt", Some("special"), "default"))
.get_matches_from(vec![
"prog", "--opt", "special"
]);
assert_eq!(m.value_of("other"), Some("default"));
In addition you can add a validator to your operation OR convert your valid operation values into flags.
Here's an example converting your match arm values into individual flags (smaller example for clarity).
extern crate clap;
use clap::{Arg,App};
fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
//Sets the command line interface of the program.
App::new("something")
.version("0.1")
.arg(Arg::with_name("rename")
.help("renames something")
.short("r")
.long("rename"))
.arg(Arg::with_name("prepend")
.help("prepends something")
.short("p")
.long("prepend"))
.arg(Arg::with_name("append")
.help("appends something")
.short("a")
.long("append"))
.get_matches()
}
#[derive(Debug)]
enum Operation {
Rename,
Append,
Prepend,
}
fn main() {
let matches = command_line_interface();
let operation = if matches.is_present("rename") {
Operation::Rename
} else if matches.is_present("prepend"){
Operation::Prepend
} else {
//DEFAULT
Operation::Append
};
println!("Value of operation is {:?}",operation);
}
I hope this helps!
EDIT:
You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.
let app_m = App::new("git")
.subcommand(SubCommand::with_name("clone"))
.subcommand(SubCommand::with_name("push"))
.subcommand(SubCommand::with_name("commit"))
.get_matches();
match app_m.subcommand() {
("clone", Some(sub_m)) => {}, // clone was used
("push", Some(sub_m)) => {}, // push was used
("commit", Some(sub_m)) => {}, // commit was used
_ => {}, // Either no subcommand or one not tested for...
}

Why is this hashmap search slower than expected?

What is the best way to check a hash map for a key?
Currently I am using this:
let hashmap = HashMap::<&str, &str>::new(); // Empty hashmap
let name = "random";
for i in 0..5000000 {
if !hashmap.contains_key(&name) {
// Do nothing
}
}
This seems to be fast in most cases and takes 0.06 seconds when run as shown, but when I use it in this following loop it becomes very slow and takes almost 1 min on my machine. (This is compiling with cargo run --release).
The code aims to open an external program, and loop over the output from that program.
let a = vec!["view", "-h"]; // Arguments to open process with
let mut child = Command::new("samtools").args(&a)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let collect_pairs = HashMap::<&str, &str>::new();
if let Some(ref mut stdout) = child.stdout {
for line in BufReader::new(stdout).lines() {
// Do stuff here
let name = "random";
if !collect_pairs.contains_key(&name) {
// Do nothing
}
}
}
For some reason adding the if !collect_pairs.contains_key( line increases the run time by almost a minute. The output from child is around 5 million lines. All this code exists in fn main()
EDIT
This appears to fix the problem, resulting in a fast run time, but I do not know why the !hashmap.contains_key does not work well here:
let n: Option<&&str> = collect_pairs.get(name);
if match n {Some(v) => 1, None => 0} == 1 {
// Do something
}
One thing to consider is that HashMap<K, V> uses a cryptographically secure hashing algorithm by default, so it will always be a bit slow by nature.
get() boils down to
self.search(k).map(|bucket| bucket.into_refs().1)
contains_key is
self.search(k).is_some()
As such, that get() is faster for you seems strange to me, it's doing more work!
Also,
if match n {Some(v) => 1, None => 0} == 1 {
This can be written more idiomatically as
if let Some(v) = n {
Ive found my problem, Im sorry I didnt pick up until now. I wasnt checking the return of if !collect_pairs.contains_key(&name) properly. It returns true for some reason resulting in the rest of the if block being run. I assumed it was evaluating to false. Thanks for the help

Resources