How to use values from an array in matching ranges of values with ..= in rust? - 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

Related

How to define a macro vecvec to initialize a vector of vectors?

Just like vec![2,3,4], can we define a similar macro vecvec to initialize vector of vector. Eg.
let vv0 = vecvec![[2,3,4],[5,6,7]]; // vec of 2 vecs
let vv1 = vecvec![[1,2,3]];
let vv2 = vecvec![[1,2,3], []];
let vv3 = vecvec![[1,3,2]; 2];
You just need to think through the problem. You really only have 2 main cases. The first case being if elements are listed (Ex: a, b, c) and the second where a single value and length are given (Ex: a; b). We can even check our work by reading the documentation for vec!. In the documentation we can see vec! is defined as follows:
macro_rules! vec {
() => { ... };
($elem:expr; $n:expr) => { ... };
($($x:expr),+ $(,)?) => { ... };
}
As you can see, they have 3 cases. We didn't specify the the case were no items are included, but that does not really matter since your macro can call vec! and have it handle that case for you.
We can just copy the cases in their macro and add the functionality inside. The only other issue that might stop you is that [a, b, c] is an expression in of itself. Luckily we can just skip that by specifying items as requiring brackets and pick out the items ourselves before passing them off to vec!.
macro_rules! vecvec {
([$($elem:expr),*]; $n:expr) => {{
let mut vec = Vec::new();
vec.resize_with($n, || vec![$($elem),*]);
vec
}};
($([$($x:expr),*]),* $(,)?) => {
vec![$(vec![$($x),*]),*]
};
}
Instead of defining a new macro. You can initialize the vector of the vector.
In the example below, I'm explicitly setting type. It's not necessary but a good practice.
let vv0:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5,6,7]];
let vv1:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5]];
let vv2:Vec<Vec<u32>> = vec![vec![],vec![5,6,7]];
let vv3:Vec<Vec<u32>> = vec![vec![2,3,4],vec![]];

String match with option in Rust inside Some [duplicate]

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.

Accept multiple values on proc macro attribute

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!(),
};

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.

get title from Zotero item

Still very new to Rust, trying to understand how to extract the title of a JournalArticle using the Zotero crate.
I've got this, and can confirm the item is retrieved successfully:
let zc = ZoteroCredentials::new();
let z = ZoteroInit::set_user(&zc.api_id, &zc.api_key);
let item = z.get_item(item_id, None).unwrap();
From here, I see that an item.data is an ItemType, specifically a JournalArticleData. But I'm fundamentally not quite understanding how to either a) serialize this to JSON, or b) access .title as a property.
For context, this would be the result of a Rocket GET route.
Any help would much appreciated!
It sounds like the part you're missing is how to use pattern matching on an enum. I'm not familiar with zotero so this is all based on the docs, with verbose type annotations to be explicit about what I think I'm doing:
use zotero::data_structure::item::{Item, ItemType, JournalArticleData};
let item: Item = z.get_item(item_id, None).unwrap();
// Now we must extract the JournalArticle from the ItemType, which is an enum
// and therefore requires pattern matching.
let article: JournalArticleData = match item.data {
ItemType::JournalArticle(a) => a,
something_else => todo!("handle wrong type of item"),
}
let title: String = article.title;
(The match could also be written as an if let just as well.)
You could also use pattern matching to go through the entire structure, rather than only the enum which requires it:
match z.get_item(item_id, None).unwrap() {
Item {
data: ItemType::JournalArticle(JournalArticleData {
title,
..
}),
..
} => {
// Use the `title` variable here
},
something_else => todo!("handle wrong type of item"),
}

Resources