Destructing a string in Rust - rust

How can I do a complex pattern matching on a string? Something like this:
let res = match data.as_ref() {
"aaa" => "this is aaa",
"bbb" => "this is bbb ",
//...........
//aaa some_data_here
"bbb {data2}" => &format!("this is 'bbb' + some data: {}", data2)
x => &format!("this is not a pattern I know of, it is {}", x),
};

There's no such thing built-in in Rust, so you have to roll your own.
In simple cases you can use slices and helper methods like starts_with(), but if the patterns are more complex, try using regex or parser crates.
match supports "guards" that allow you to run extra code to refine a match:
match string {
s if s.starts_with("bbb ") => format!("this is 'bbb' + some data: {}", &s[4..])
}

Related

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.

How to use values from an array in matching ranges of values with ..= in rust?

I'm learning rust and I found something I can't just find in google.
I was experimenting with match and I wanted to use values from an array with the ..= syntax.
I know I'm doing something wrong, but I only know Js and Python and I feel I'm missing something basic that it's just known but not explained.
pub fn match_statement() {
println!("Match statement----------------------------------");
let mut country_code=0;
let country_codes_range: [i64; 4] = [1,999,50,66];
let country = match country_code {
34 => "Spain",
46 => "Sweden",
country_codes_range[0]..=country_codes_range[1] => "unknown",
_ => "invalid",
};
country_code=invalid_country;
println!(
"The {} country code is {} because is out of the range [{},{}]",
country, invalid_country, country_codes_range[0], country_codes_range[1]
);
}
the error I get is:
expected one of =>, #, if, or |, found [
on the line
country_codes_range[0]..=country_codes_range[1] => "unknown"
I don't know if the issue lies in my calling of items of the array, an incorrect use of ..= or another thing
Also, I guess I would get a similar error if I used a tuple instead of an array?
Thanks for your help
Rust needs to know the "values" of each match arm at compile time, so what you're describing isn't possible, instead you'll get an error saying runtime values cannot be references in patterns.
If you know what country_codes_range will be at compile time, you can make it available at compile time using const:
fn match_statement() {
let country_code = 123;
const COUNTRY_CODES_RANGE: [i64; 4] = [1, 999, 50, 66];
const FIRST: i64 = COUNTRY_CODES_RANGE[0];
const SECOND: i64 = COUNTRY_CODES_RANGE[1];
let country = match country_code {
34 => "spain",
46 => "sweden",
FIRST..=SECOND => "unknown",
_ => "invalid",
};
// ...
}
Note, the intermediate consts FIRST and SECOND are needed because currently Rust's parser doesn't support the a[i] syntax in patterns, though that is a separate problem to having a match use runtime values

What is the difference between the switch and match syntax?

Some languages have a switch expression/statement and some have a match statement. Is there a difference in this semantically, or is it just a different syntax for something that is fundamentally the same.
For example:
Rust has match:
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => 25,
}
(Taken from https://doc.rust-lang.org/book/ch06-02-match.html#the-match-control-flow-operator.)
Java has switch:
switch coin {
case Penny:
return 1;
break;
case Nickel:
return 5;
break;
case Dime:
return 10;
break;
case Quarter:
return 25;
break;
}
(A piece of equivalent code.)
Caveat: this varies wildly depending on the language, of course.
Here I'll work with the Java switch statement, since it's a commonly-used language and its switch semantics seem roughly representative.
A few key differences are:
match is exhaustive (i.e. you have to be able to prove to the compiler that exactly one branch is matched)
match is an expression (although arguably this is more a Rust feature than a match feature), i.e.:
let x = 123;
let s = match x {
0 => "zero",
1 => "one",
_ => "something else",
};
println!("{}", s); // prints "something else"
match performs destructuring:
let x = Some(123);
let s = match x {
None => "nothing".to_string(),
Some(x) => format!("the number: {}", x),
};
println!("{}", s); // prints "the number: 123"
However, you really should take a look at the docs for match and compare them to the docs for the relevant switch feature in another language. These are just the obvious differences when comparing against C-like switches.

What is the correct & idiomatic way to check if a string starts with a certain character in Rust?

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.

Can you write a macro to invoke the default() operator in rust?

Something like:
macro_rules! default(
($T:ty, $($args:expr)*) => (
$T { $($args)*, ..Default::default() };
);
)
...but with a magical type instead of expr, so you could do something like:
let p = default!(CItem, _z: ~"Hi2", x: 10);
let q = default!(CItem, _z, ~"Hi2", x, 10);
let r = default!(CItem, { _z: ~"Hi2", x: 10 });
Or something along those lines?
Is there any macro symbol that simply picks up a literal block of characters without first parsing it as a type/expr/etc?
(I realize you'd typically just write a CItem::new(), but this seems like a nice situation in some circumstances)
Macros can have multiple pattern to match the syntax, so you have to write a seperate pattern for every case seperatly like this:
macro_rules! default(
($t:ident, $($n:ident, $v:expr),*) => {
$t { $($n: $v),*, ..Default::default() }
};
($t:ident, $($n:ident: $v:expr),*) => {
default!($t, $($n, $v),*)
};
)
As you can see there are two patterns, the first matching pairs seperated by comma and the second one pairs seperated by colon.

Resources