This question already has answers here:
How can I pattern match against an Option<String>?
(4 answers)
Closed 8 months ago.
Problem description
I'm trying to match option string with match statement
let option_string = Some(String::from("Bob"));
match option_string {
Some("Mike") => false,
Some("Bob") => true,
_ => false,
}
And, obviously, got an error expected struct 'String, found '&str'.
I tried to change it into string cast
Some("Mike".to_string()) => false
// Or
Some(String::from("Mike")) => false
But faced with a different error: 'fn' calls are not allowed in patterns
The only working way is to place Mike into a variable before Some
let mike = String::from("Mike");
// and in match
Some(mike) => true,
Question
There is a more elegant way to match String but not string literals in match cases with Option value?
I found the answer but it doesn't look elegant enough too. But is it only one possible way to not create extra variables or functions?
let mike = String::from("Mike");
// and in match
Some(mike) => true,
This one is actually a misconception, I'm afraid. Variables are not allowed on the left side of a match expression. Having a name on the left side actually creates a new variable that contains the matched content. So the mike variable in your match clause matches everything and then carries the matched String; it is not the same variable as the outer mike variable.
Pay attention to this code example:
fn main() {
let option_string = Some(String::from("Bob"));
// Note how this line gets the compiler warning "unused variable".
// You could leave this line out completely and it would still
// compile.
let mike = String::from("Mike");
let result = match option_string {
Some(mike) => {
println!("Matched 'Mike': {}", mike);
true
}
_ => false,
};
println!("{:?}", result);
}
Matched 'Mike': Bob
true
In general, you can only match against compile time constants. If you want to compare two variables, you have to use if instead.
Solution
That said, your first example is quite easy to fix:
fn main() {
let option_string = Some(String::from("Bob"));
let result = match option_string.as_deref() {
Some("Mike") => false,
Some("Bob") => true,
_ => false,
};
println!("{:?}", result);
}
true
Note the .as_deref(), which borrows an Option<&str> from the Option<String>, making it compatible with the string literal match expressions.
Related
This question already has answers here:
How can I pull data out of an Option for independent use?
(3 answers)
Closed 7 months ago.
I want to print out the value of sys.host_name()
let hostname = sys.host_name();
println!("{:?}", hostname);
Output: "Some("arch")"
Expected output: "arch"
How can i print the "raw" string?
It appears (not sure what library you are using) sys.host_name() does not return a string, rather an Option<String>, which implies the function could fail (and return None instead of Some("arch").
println! is printing the full type. If you just wish to print the contained string, you need to extract the string from the Option. One possible way:
let possible_hostname = sys.host_name();
// Check if the hostname is Some or None
if let Some(hostname) = possible_hostname {
println!("{:?}", hostname);
}
else {
println!("hostname could not be determined");
}
If you would prefer your program to just panic if the hostname is None, you can "unwrap" the option:
let hostname = sys.host_name().unwrap();
println!("{:?}", hostname);
I wanted to be able to retrieve the content from an attribute like this:
#[foreign_key(table = "some_table", column = "some_column")]
This is how I am trying:
impl TryFrom<&&Attribute> for EntityFieldAnnotation {
type Error = syn::Error;
fn try_from(attribute: &&Attribute) -> Result<Self, Self::Error> {
if attribute.path.is_ident("foreign_key") {
match attribute.parse_args()? {
syn::Meta::NameValue(nv) =>
println!("NAME VALUE: {:?}, {:?}, {:?}",
nv.path.get_ident(),
nv.eq_token.to_token_stream(),
nv.lit.to_token_stream(),
),
_ => println!("Not interesting")
}
} else {
println!("No foreign key")
}
// ... More Rust code
}
Everything works fine if I just put in there only one NameValue. When I add the comma,
everything brokes.
The only error:
error: unexpected token
How can I fix my logic to enable the possibility of have more than just one NameValue?
Thanks
UPDATE: While writing this answer, I had forgotten that Meta has List variant as well which gives you NestedMeta. I would generally prefer doing that instead of what I did in the answer below for more flexibility.
Although, for your particular case, using Punctuated still seems simpler and cleaner to me.
MetaNameValue represents only a single name-value pair. In your case it is delimited by ,, so, you need to parse all of those delimited values as MetaNameValue instead.
Instead of calling parse_args, you can use parse_args_with along with Punctuated::parse_terminated:
use syn::{punctuated::Punctuated, MetaNameValue, Token};
let name_values: Punctuated<MetaNameValue, Token![,]> = attribute.parse_args_with(Punctuated::parse_terminated).unwrap(); // handle error instead of unwrap
Above name_values has type Punctuated which is an iterator. You can iterate over it to get various MetaNameValue in your attribute.
Updates based on comments:
Getting value out as String from MetaNameValue:
let name_values: Result<Punctuated<MetaNameValue, Token![,]>, _> = attr.parse_args_with(Punctuated::parse_terminated);
match name_values {
Ok(name_value) => {
for nv in name_value {
println!("Meta NV: {:?}", nv.path.get_ident());
let value = match nv.lit {
syn::Lit::Str(v) => v.value(),
_ => panic!("expeced a string value"), // handle this err and don't panic
};
println!( "Meta VALUE: {:?}", value )
}
},
Err(_) => todo!(),
};
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...
}
I am trying to make reimplementation of first 2 Final Fantasy games using original data from various platforms. I want to get 2 program arguments using the getopts crate and handle both of them by using match but it just executes the first match element. I think I screwed up something with types.
Maybe there is another way to do it? I am lost using the official Rust docs and any tutorials on internet are not really noob-friendly.
Here is the code:
let args: Vec<String> = env::args().map(|x| x.to_string()).collect();
if(args.len() < 3) {
println!("=====ERROR=====\nInvalid number of parameters\nExpected: <gamename> <gamerom>\nType in: 'help me' to get some help.");
process::exit(1);
}
let ref game = args[1];
let ref rom = args[2];
match game {
help => {
println!("=====HELP======");
match rom {
list => println!("Available games: ff1, ff2\nAvailable roms: ff1_j_msx, ff1_j_nes, ff1_u, ff1and2, ff2_j, ff2_u_proto"),
me => println!("Available help commands:\nlist -> List of available games and roms.\nme -> This help"),
_ => println!("=====ERROR=====\nInvalid help command.")
}
},
_ => println!("=====ERROR=====\nInvalid game, type in 'help me' to get some help.")
}
You really need to read the compilers error and warning messages. This code has seven warnings. If you had addressed any of them you'd be a lot closer to fixing the problem yourself. If you'd fixed all of them, your problem would be gone.
Here's a representative warning where the compiler tells you exactly what the problem is:
warning: unreachable pattern
--> src/main.rs:24:5
|
24 | _ => println!("=====ERROR=====\nInvalid game, type in 'help me' to get some help.")
| ^ this is an unreachable pattern
|
= note: #[warn(unreachable_patterns)] on by default
note: this pattern matches any value
--> src/main.rs:15:5
|
15 | help => {
| ^^^^
When you use just help, that creates a new variable with the value you are matching on. In this case, it matches everything, so the subsequent arms can never match.
Instead, you need to match against a string literal:
match game.as_str() {
"help" => {
match rom.as_str() {
"list" => /* ... */,
"me" => /* ... */,
_ => /* ... */,
}
},
_ => /* ... */,
}
I'd strongly encourage you to go back and re-read The Rust Programming Language. It's where a lot of the beginner documentation is kept. Specifically, you should read from the beginning and then up through the chapter on match and the chapter on patterns.
I want to check whether a string starts with some chars:
for line in lines_of_text.split("\n").collect::<Vec<_>>().iter() {
let rendered = match line.char_at(0) {
'#' => {
// Heading
Cyan.paint(*line).to_string()
}
'>' => {
// Quotation
White.paint(*line).to_string()
}
'-' => {
// Inline list
Green.paint(*line).to_string()
}
'`' => {
// Code
White.paint(*line).to_string()
}
_ => (*line).to_string(),
};
println!("{:?}", rendered);
}
I've used char_at, but it reports an error due to its instability.
main.rs:49:29: 49:39 error: use of unstable library feature 'str_char': frequently replaced by the chars() iterator, this method may be removed or possibly renamed in the future; it is normally replaced by chars/char_indices iterators or by getting the first char from a subslice (see issue #27754)
main.rs:49 let rendered = match line.char_at(0) {
^~~~~~~~~~
I'm currently using Rust 1.5
The error message gives useful hints on what to do:
frequently replaced by the chars() iterator, this method may be removed or possibly renamed in the future; it is normally replaced by chars/char_indices iterators or by getting the first char from a subslice (see issue #27754)
We could follow the error text:
for line in lines_of_text.split("\n") {
match line.chars().next() {
Some('#') => println!("Heading"),
Some('>') => println!("Quotation"),
Some('-') => println!("Inline list"),
Some('`') => println!("Code"),
Some(_) => println!("Other"),
None => println!("Empty string"),
};
}
Note that this exposes an error condition you were not handling! What if there was no first character?
We could slice the string and then pattern match on string slices:
for line in lines_of_text.split("\n") {
match &line[..1] {
"#" => println!("Heading"),
">" => println!("Quotation"),
"-" => println!("Inline list"),
"`" => println!("Code"),
_ => println!("Other")
};
}
Slicing a string operates by bytes and thus this will panic if your first character isn't exactly 1 byte (a.k.a. an ASCII character). It will also panic if the string is empty. You can choose to avoid these panics:
for line in lines_of_text.split("\n") {
match line.get(..1) {
Some("#") => println!("Heading"),
Some(">") => println!("Quotation"),
Some("-") => println!("Inline list"),
Some("`") => println!("Code"),
_ => println!("Other"),
};
}
We could use the method that is a direct match to your problem statement, str::starts_with:
for line in lines_of_text.split("\n") {
if line.starts_with('#') { println!("Heading") }
else if line.starts_with('>') { println!("Quotation") }
else if line.starts_with('-') { println!("Inline list") }
else if line.starts_with('`') { println!("Code") }
else { println!("Other") }
}
Note that this solution doesn't panic if the string is empty or if the first character isn't ASCII. I'd probably pick this solution for those reasons. Putting the if bodies on the same line as the if statement is not normal Rust style, but I put it that way to leave it consistent with the other examples. You should look to see how separating them onto different lines looks.
As an aside, you don't need collect::<Vec<_>>().iter(), this is just inefficient. There's no reason to take an iterator, build a vector from it, then iterate over the vector. Just use the original iterator.