This question already has an answer here:
How to match on the start of a string?
(1 answer)
Closed 5 months ago.
I have a string variable "distro"
let distro = get_distro().unwrap().name;
The value of this variable is for example "Arch Linux".
Now i want to check in a match, if the variable contains "arch".
match distro.to_lowercase() {
"arch" => //...
}
To use a condition in a match at rust you need to add an if to your match. In this case it would look like
match &distro.to_lowercase() {
x if x.contains("arch") => //...
}
As per I understand you want to check something in the string. For this purpose, match statement is not quite what you are looking for. For string there exists contains method.
Example:
let disto_name = "Arch Linux";
if distro_name.to_ascii_lowercase().contains("arch") {
println!("This is arch linux");
} else {
println!("This is not arch linux");
}
You can do like this:
let distro = get_distro().unwrap().name;
if distro.to_lowercase().as_str().contains("arch") {
println!("0")
}
Playground
Also you can make it simpler, like this:
const DISTRO_NAMES: [&str; 2] = ["arch", "ubuntu"];
fn detector(distro: String) {
for item in DISTRO_NAMES {
if distro.to_lowercase().contains(item) {
println!("the distro is = {item}")
}
}
}
fn main() {
let distro = get_distro().unwrap().name;
detector(distro.to_string())
}
with this solution, you just need to add your distros to the DISTRO_NAMES array.
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);
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.
Is there a more idiomatic way to express something like the following?
fn main() {
let mut foo: Option<u8> = None;
match foo {
Some(foo_val) if ! (foo_val < 5) /* i.e. the negation of my acceptance condition */ => {}
_ => { foo.replace(5); }
}
}
It seems like most of the time there's an alternative to having an arm that doesn't do anything, but I've been unable to find one for this particular case.
What I'd like to say is the more direct if foo.is_none() || /* some way to extract and test the inner value */ { ... }, or perhaps some chaining trick that's eluding me.
// in None case
// │ in Some(_) case
// ┌┴─┐ ┌───────────────────┐
if foo.map_or(true, |foo_val| foo_val < 5) {
// ...
}
For more information see Option::map_or.
There are many ways to do it. One of the simplest (and arguably most readable) is something like this:
if foo.unwrap_or(0) < 5 {
...
}
The above will be true in both cases:
when foo is Some with a value smaller than 5;
when foo is None.
In some more complex scenarios, where the "default" value needs to be calculated and performance is critical, you might want to consider unwrap_or_else.
As Lukas suggested, the map_or method can also be used. Note that arguments passed to map_or are eagerly evaluated, so if performance is critical, you might want to consider map_or_else as an alternative.
You can do it with filter (using the negation of your condition) and is_none:
if foo.filter(|&x| !(x < 5)).is_none() {
// Here either foo was None or it contained a value less than 5
}
I'm not sure I completely understand your question but you can try something like that:
fn main() {
let foo: Option<u8> = None;
let result = foo.filter(|foo_val| !(*foo_val < 5) ).unwrap_or(5);
println!("Result: {result}");
}
More example on Playground
The matches! macro seems like a good fit:
if !matches!(foo, Some(a) if a>=5) { foo.replace(5) }
Rust Playground
I'll throw in another solution just for fun....
foo = foo.
or(Some(5)). // if None return Some(5)
map(|x| if x<5 { 5 } else { x });
or (for this specific example)
foo = foo.
or(Some(5)). // if None return Some(5)
map(|x| u8::max(x, 5));
With filter and or,
foo = foo.filter(|a| *a >= 5)
.or(Some(5));
There is the unstable method Option::is_some_and that has been built for exactly this purpose:
if foo.is_some_and(|foo_val| foo_val < 5) {
// ...
}
As it's unstable, it's currently only usable on nightly. See the tracking issue for up to date information.
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'm parsing a vector of tokens, each of enum type. This means I get a lot of code like:
if v.len() >= 3 {
if let Token::Type1(value1) = &v[0] {
if let Token::Type2(value2) = &v[1] {
if let Token::Type(value3) = &v[2] {
return Parsed123(value1, value2, value3);
}
}
}
}
This is pretty ugly - and I've worked out that I can do this to make it a little nicer:
if v.len() >= 3 {
if let (Token::Type1(value1), Token::Type2(value2), Token::Type3(value3)) =
(&v[0], &v[1], &v[2])
{
return Parsed123(value1, value2, value3);
}
}
But honestly, its not much better.
However, there's some closed issues / RFCs for chaining these conditions and "if let" bits in what feels a lot more ergonomic way -- Tracking issue for eRFC 2497 "if- and while-let-chains take 2" and Support && in if let expressions -- this would let me write something like:
if v.len() >= 3 &&
let Token::Type1(value1) = &v[0] &&
let Token::Type2(value2) = &v[1] &&
let Token::Type3(value3) = &v[2]
{
return Parsed123(value1, value2, value3);
}
However, I can't seem to get this to compile in my copy of nightly Rust with edition="2018" (exact version is 1.32.0-nightly (653da4fd0 2018-11-08)). So either I've got the syntax wrong or I've misinterpreted the RFCs / issues and this feature hasn't landed yet. Either way, I'd love some info on how this feature stands.
RFC #2497 has not been implemented yet. The GitHub issue you linked is only for describing how to deal with the ambiguity.
To enable the second interpretation in the previous section a warning must be emitted in Rust 2015 informing the user that [...] will both become hard errors, in the first version of Rust where the 2018 edition is stable, without the let_chains features having been stabilized.
So no, you cannot use the syntax yet, but instead use tuples as a workaround, as you already did.
if v.len() >= 3 {
if let (Token::Type1(value1), Token::Type2(value2), Token::Type3(value3)) =
(&v[0], &v[1], &v[2])
{
return Parsed123(value1, value2, value3);
}
}
While hellow is correct that RFC #2497 is not yet supported in 2018 (and 2015), I felt the if_chain library mentioned by Michail was worthy of an answer.
The if_chain library provides a macro that transforms some code that is almost in the form of RFC #2497 into valid Rust.
You can write:
if_chain! {
if v.len() >= 3;
if let Token::Type1(value1) = &v[0];
if let Token::Type2(value2) = &v[1];
if let Token::Type3(value3) = &v[2];
then {
return Parsed123(value1, value2, value3);
}
}
which the compiler treats as:
if v.len() >= 3 {
if let Token::Type1(value1) = &v[0] {
if let Token::Type2(value2) = &v[1] {
if let Token::Type(value3) = &v[2] {
return Parsed123(value1, value2, value3);
}
}
}
}
As mentioned in the comments by L.F., in 2020 there is now another alternative. It still doesn't give us chained if let, but does allow us to match on slices - which is enough to make this example quite neat. The code could now be written as
if let [Token::Type1(value1), Token::Type2(value2), Token::Type3(value3), ..] = v {
return Parsed123(value1, value2, value);
}