Macro to match specific identifiers for inline lookups - rust

Okay, writing my absolute first project in Rust. So, I have something like the following sort of setup:
use phf;
use std::str;
struct Values {
a: Option<char>,
b: Option<char>,
c: Option<char>
}
static MAPPING: phf::Map<&'static str, Values> = phf::phf_map! {
"some_key" => Values {
a: Some('a'),
b: Some('b'),
c: None
},
"some_other_key" => Values {
a: Some('1'),
b: None,
c: None
},
"some_third_key" => Values {
a: None,
b: Some('x'),
c: Some('y')
}
}
static NULL_VALUES: Values = Values {
a: None,
b: None,
c: None
}
// Should return a &str for any given key/val
#[macro_export]
macro_rules! get_value {
($key: ident, $val: ident) => {{
use crate::values::MAPPING;
use std::str;
let result = MAPPING.get("$key");
if let Some(r) = result {
if let Some(c) = r.$val {
if let Ok(s) = str::from_utf8(%[c as u8]) { s } else { "" }
} else { "" }
} else { "" }
}}
}
Which, it works, but it's just so much code and seeming like a whole lot of runtime overhead for no other reason than to organise some static values to avoid having to remember them all (in reality there are quite a lot and they're all raw codepoints). What I would love to be able to do is to just have a macro that takes a specific key/val and simply inlines either a known value or an empty value, but as far as I can tell there isn't any way to match a macro on a specific identifier, only any identifier... Is there any way that I can move all these lookups from runtime to compile time?

Macros can pattern match against specific identifiers — just don't use $.
macro_rules! get_value {
(some_key, a) => { Some('a') };
(some_key, b) => { Some('b') };
(some_key, c) => { None };
(some_other_key, a) => { Some(1) };
// ...
}
However, are you sure you don't want to just define a bunch of constants?
const SOME_KEY_A: Option<char> = Some('a');
const SOME_KEY_B: Option<char> = Some('b');
Or expose the Values struct you already designed, which would then be accessed like SOME_KEY.a:
const SOME_KEY: Values = Values {
a: Some('a'),
b: Some('b'),
c: None
};
That way, readers don't have to understand your macro to know that the data is just a constant. This will make your code easier to read and modify.

Related

Rust, getting values from HashMap with enum

I'm trying make one HashMap with different types. I don't want to make two different HashMaps for specific data types.
My code is bellow:
use std::collections::HashMap;
#[derive(Debug)]
enum DataTypes {
String(String),
Bool(bool),
}
fn get_hashmap() -> Result<HashMap<String, DataTypes>, ()>{
let data = HashMap::from([
("password".to_string(), DataTypes::String("password".to_string())),
("username".to_string(), DataTypes::String("Fun username".to_string())),
("is_blocked".to_string(), DataTypes::Bool(true)),
("is_confirmed".to_string(), DataTypes::Bool(false)),
]);
Ok(data)
}
fn main() {
let data = get_hashmap().unwrap();
let keys = data.keys();
println!("Keys: {:?}", &keys);
for key in keys {
let result: Option<T> = match data.get(key).unwrap() {
DataTypes::Bool(value) => Some(value),
DataTypes::String(value) => Some(value),
_ => panic!("Error!"),
};
println!("Result of matching: {:?}", &result);
}
}
Like you can see I'm trying to maching Enums to getting their values. But i have some problem of data types.
My solution for this is wrap result of matching to Some struct. But still main problem is not resolved.
So I want to make result of matching in Option class to make available unwrap().
But I don't know how i can do that correctly...
I have two question:
Can i do this better?
How can I wrap let result: Option to working state?
Some feedback:
Don't include a _ default match case if you already handle all the options. It will hide future errors.
Don't name a variable DataTypes if every member is only a single datatype. Name it DataType.
result has to be a specific type. The whole point of the enum is that you can handle the different values separately, so combining them in a result type is pointless. Although you of course can keep result a DataType object and implement Debug/Display for it, which is how I will do it in my reworked code.
While you can query the key first and then again query the value in the loop, this is quite slow. You can iterate over key-value pairs right away. That way you avoid a lot of unwrap()s, which makes your code a lot less error-prone.
use std::collections::HashMap;
#[derive(Debug)]
enum DataType {
String(String),
Bool(bool),
}
fn get_hashmap() -> Result<HashMap<String, DataType>, ()> {
let data = HashMap::from([
(
"password".to_string(),
DataType::String("password".to_string()),
),
(
"username".to_string(),
DataType::String("Fun username".to_string()),
),
("is_blocked".to_string(), DataType::Bool(true)),
("is_confirmed".to_string(), DataType::Bool(false)),
]);
Ok(data)
}
fn main() {
let data = get_hashmap().unwrap();
for (key, value) in data {
println!("{}: {:?}", key, value);
match value {
DataType::Bool(value) => {
println!("\tValue was a bool: {}", value);
// do something if the value is a bool
}
DataType::String(value) => {
println!("\tValue was a string: {}", value);
// do something if the value is a string,
} /*
* Don't include a default case. That way the compiler
* will remind you to handle additional enum entries if
* you add them in the future.
* Adding a default case is only a good practice in languages
* where matching is not exhaustive.
*/
};
}
}
username: String("Fun username")
Value was a string: Fun username
is_confirmed: Bool(false)
Value was a bool: false
is_blocked: Bool(true)
Value was a bool: true
password: String("password")
Value was a string: password
Don't worry though, you don't need to use match everywhere you use this enum, otherwise you wouldn't win much compared to two separate hashmaps. You can, instead, define shared functionality for all enum entries, and hide the match inside of it. Like this:
use std::collections::HashMap;
#[derive(Debug)]
enum DataType {
String(String),
Bool(bool),
}
impl DataType {
fn do_something(&self) {
match self {
DataType::Bool(value) => {
println!("\tDo something with boolean '{}'!", value);
}
DataType::String(value) => {
println!("\tDo something with string {:?}!", value);
}
};
}
}
fn get_hashmap() -> Result<HashMap<String, DataType>, ()> {
let data = HashMap::from([
(
"password".to_string(),
DataType::String("password".to_string()),
),
(
"username".to_string(),
DataType::String("Fun username".to_string()),
),
("is_blocked".to_string(), DataType::Bool(true)),
("is_confirmed".to_string(), DataType::Bool(false)),
]);
Ok(data)
}
fn main() {
let data = get_hashmap().unwrap();
for (key, value) in data {
println!("{}: {:?}", key, value);
value.do_something();
}
}
is_confirmed: Bool(false)
Do something with boolean 'false'!
password: String("password")
Do something with string "password"!
is_blocked: Bool(true)
Do something with boolean 'true'!
username: String("Fun username")
Do something with string "Fun username"!
If your goal is to add serialization/deserialization to your struct (as you seem to implement manually here), let me hint you towards serde, which already takes care of the majority of serialization for free.
Like in this example (which may or may not be how your struct looks like) that serializes your struct to and from JSON:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
username: String,
password: String,
is_blocked: bool,
is_confirmed: bool,
}
fn main() {
let user = User {
username: "Fun username".to_string(),
password: "password".to_string(),
is_blocked: true,
is_confirmed: false,
};
let user_serialized = serde_json::to_string(&user).unwrap();
println!("Serialized: {}", user_serialized);
let user_deserialized: User = serde_json::from_str(&user_serialized).unwrap();
println!("Name: {}", user_deserialized.username);
}
Serialized: {"username":"Fun username","password":"password","is_blocked":true,"is_confirmed":false}
Name: Fun username

How to access nested enums without full match syntax

I am using the Serde crate to deserialise a JSON file, which has a nested structure like this:
struct Nested {
a: Vec<Foo>,
b: u8,
}
struct Foo {
c: Bar,
d: Vec<f32>,
}
Struct Bar {
e: u32,
f: String,
}
Part of the applications purpose is to check for missing parameters (or incorrect types in parameters), and then display a nicely printed list of errors found in the file, so I need to handle the structure missing parameters or wrongly typed.
I came across this great post that helped solved my issue, by wrapping each parameter in an enum result that contains the value if it passed, the value if it failed, or a final enum if it was missing (since the nested structures might also be missing I wrapped them in the same enum):
pub enum TryParse<T> {
Parsed(T),
Unparsed(Value),
NotPresent
}
struct Nested {
a: TryParse<Vec<Foo>>,
b: TryParse<u8>,
}
struct Foo {
c: TryParse<Bar>,
d: TryParse<Vec<f32>>,
}
Struct Bar {
e: TryParse<u32>,
f: TryParse<String>,
}
However, I'm not sure how to access them now without unpacking every step into a match statement. For example, I can access B very easily:
match file.b {
Parsed(val) => {println!("Got parameter of {}", val)},
Unparsed(val) => {println!("Invalid type: {:?}", val)}
NotPresent => {println!("b not found")},
};
However, I'm not sure how to access the nested ones (C D E and F). I can't use Unwrap or expect since this isn't technically a 'Result', so how do I unpack these?:
if file.a.c.e.Parsed() && file.a.c.e == 32 {... //invalid
if file.a.d && file.a.d.len() == 6... //invalid
I know in a way this flies against rust's 'handle every outcome' philosophy, and I want to handle them, but I want to know if there is a nicer way than 400 nested match statements (the above example is very simplified, the files I am using have up to 6 nested layers, each parameter in the top node has at least 3 layers, some are vectors as well)…
Perhaps I need to implement a function similar to unwrap() on my 'TryParse'? or would it be better to wrap each parameter in a 'Result', extend that with the deserialise trait, and then somehow store the error in the Err option that says if it was a type error or missing parameter?
EDIT
I tried adding the following, some of which works and some of which does not:
impl <T> TryParse<T> {
pub fn is_ok(self) -> bool { //works
match self {
Self::Parsed(_t) => true,
_ => false,
}
}
pub fn is_absent(self) -> bool { //works
match self {
Self::NotPresent => true,
_ => false,
}
}
pub fn is_invalid(self) -> bool { //works
match self {
Self::Unparsed(_) => true,
_ => false,
}
}
pub fn get(self) -> Result<T, dyn Error> { //doesnt work
match self {
Self::Parsed(t) => Ok(t),
Self::Unparsed(e) => Err(e),
Self::NotPresent => Err("Invalid")
}
}
}
I can't believe it is this hard just to get the result, should I just avoid nested enums or get rid of the TryParse enums/ functions all together and wrap everything in a result, so the user simply knows if it worked or didn't work (but no explanation why it failed)
Implementing unwrap() is one possibility. Using Result is another, with a custom error type. You can deserialize directly into result with #[serde(deserialize_with = "...")], or using a newtype wrapper.
However, a not-enough-used power of pattern matching is nested patterns. For example, instead of if file.a.c.e.Parsed() && file.a.c.e == 32 you can write:
if let TryParse::Parsed(a) = &file.a {
// Unfortunately we cannot combine this `if let` with the surrounding `if let`,
// because `Vec` doesn't support pattern matching (currently).
if let TryParsed::Parsed(
[Foo {
c:
TryParse::Parsed(Bar {
e: TryParse::Parsed(32),
..
}),
..
}, ..],
) = a.as_slice()
{
// ...
}
}
May not be the most Rust-y way of doing it, but for those like me moving from another language like C/Python/C++, this is the way I have done it that still allows me to quickly validate if I have an error and use the match syntax to handle it. Thanks to #Chayim Friedman for assisting with this, his way is probably better but this made the most sense for me:
#[derive(Debug)]
pub enum TryParse<T> {
Parsed(T),
Unparsed(Value),
NotPresent
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for TryParse<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match Option::<Value>::deserialize(deserializer)? {
None => Ok(TryParse::NotPresent),
Some(value) => match T::deserialize(&value) {
Ok(t) => Ok(TryParse::Parsed(t)),
Err(_) => Ok(TryParse::Unparsed(value)),
},
}
}
}
impl <T> TryParse<T> {
//pub fn is_ok(self) -> bool { ---> Use get().is_ok(), built into result
// match self {
// Self::Parsed(_t) => true,
// _ => false,
// }
//}
pub fn is_absent(self) -> bool {
match self {
Self::NotPresent => true,
_ => false,
}
}
pub fn is_invalid(self) -> bool {
match self {
Self::Unparsed(_) => true,
_ => false,
}
}
pub fn get(&self) -> Result<&T, String> {
match self {
Self::Parsed(t) => Ok(t),
Self::Unparsed(v) => Err(format!("Unable to Parse {:?}", v)),
Self::NotPresent => Err("Parameter not Present".to_string())
}
}
// pub fn get_direct(&self) -> &T {
// match self {
// Self::Parsed(t) => t,
// _ => panic!("Can't get this value!"),
// }
// }
}
match &nested.a.get().unwrap()[1].c.get.expect("Missing C Parameter").e{
Parsed(val) => {println!("Got value of E: {}", val)},
Unparsed(val) => {println!("Invalid Type: {:?}", val)}
NotPresent => {println!("Param E Not Found")},
};
//Note the use of '&' at the beginning because we need to borrow a reference to it
I know I need to change my mindset to use the rust way of thinking, and I am completely open to other suggestions if they can demonstrate some working code.

Is there a macro I can use to expect a variant of an enum and extract its data?

Given an enum like
struct Earth { water: usize }
struct Mars { redness: usize }
enum World {
Mars(Mars),
Earth(Earth),
}
A common pattern I write is
fn something_expecting_mars(planet: World) {
let mars = match planet {
World::Mars(data) => data,
_ => panic!("Shouldn't be here now"),
}
}
Is there a macro I can use to expect a variant of an enum and subsequently extract its data?
// rewriting to this
let mars = expect_v!(planet, World::Mars);
The standard library provides a macro for testing a match, but not one for extracting a value. However, it's fairly easy to write one:
macro_rules! expect_v {
($e:expr, $p:path) => {
match $e {
$p(value) => value,
_ => panic!("expected {}", stringify!($p)),
}
};
}
Playground
As suggested in answers to the related question brought up in the comments, you might want to decouple value extraction from the panic. In that case, return an Option instead and let the callers panic if they wish by calling unwrap():
macro_rules! extract {
($e:expr, $p:path) => {
match $e {
$p(value) => Some(value),
_ => None,
}
};
}
// ...
fn something_expecting_mars(planet: World) {
let mars = extract!(planet, World::Mars).unwrap();
}
Anything wrong with just using if let instead of match?
mars = if let World::Mars(data) = planet { data } else { panic!("Woot woot")}

How do I check if both variables are both Some?

I am confused about the Some(T) keyword.
I want to check for two variables, if the value is defined (not None). If that is the case, the value of this variables is processed.
I know the match pattern which works like this:
match value {
Some(val) => println!("{}", val),
None => return false,
}
If I use this pattern, it will get very messy:
match param {
Some(par) => {
match value {
Some(val) => {
//process
},
None => return false,
}
},
None => return false,
}
This can't be the right solution.
The is a possibility, to ask if the param and value is_some() That would effect code like that:
if param.is_some() && value.is_some() {
//process
}
But if I do it like that, I always have to unwrap param and value to access the values.
I thought about something like this to avoid that. But this code does not work:
if param == Some(par) && value == Some(val) {
//process
}
The idea is that the values are accessible by par and val like they are in the match version.
Is there any solution to do something like this?
If I have several Option values to match, I match on a tuple of the values:
enum Color {
Red,
Blue,
Green,
}
fn foo(a: Option<Color>, b: Option<i32>) {
match (a, b) {
(Some(Color::Blue), Some(n)) if n > 10 => println!("Blue large number"),
(Some(Color::Red), _) => println!("Red number"),
_ => (),
}
}
fn main() {
foo(Some(Color::Blue), None);
foo(Some(Color::Blue), Some(20));
}
This allows me to match the combinations that are interesting, and discard the rest (or return false, if that is what you want to do).
If your function is processing multiple Option values, and would like to discard them if they're not Some, your function could return an Option itself:
fn foo(param: Option<usize>, value: Option<usize>) -> Option<usize> {
let result = param? + value?;
Some(result)
}
This will short-circuit the function in case there's a None value stored in either param or value.
Please read the book for more information on the ? operator.
If your function can't return an Option, you can still get away with destructuring using if let or match:
let x = if let (Some(p), Some(v)) = (param, value) {
p + v
} else {
return 0;
}
let x = match (param, value) {
(Some(p), Some(v)) => p + v,
(Some(p), _) => p,
(_, Some(v) => v,
_ => return 0,
}
Please read What is this question mark operator about? for more information on the ? operator
Please read this chapter in Rust by Example for more information on destructuring multiple things at once
There's a couple more alternatives not yet listed:
If you're willing to use experimental features (and hence the nightly compiler) you can use a try block as an alternative of extracting a function.
#![feature(try_blocks)]
fn main() {
let par: Option<f32> = Some(1.0f32);
let value: Option<f32> = Some(2.0f32);
let x: Option<f32> = try { par? + value? };
println!("{:?}", x);
}
Another alternative is to use map which only applies if the value is not None
let x: Option<f32> = par.map(|p| value.map(|v| p + v));

Consolidating multiple copies of a character at start of string into one in Rust

I'm working on a parser for a mini language, and I have the need to differentiate between plain strings ("hello") and strings that are meant to be operators/commands, and start with a specific sigil character (e.g. "$add").
I also want to add a way for the user to escape the sigil, in which a double-sigil gets consolidated into one, and then is treated like a plain string.
As an example:
"hello" becomes Str("hello")
"$add" becomes Operator(Op::Add)
"$$add" becomes Str("$add")
What would be the best way to do this check and manipulation? I was looking for a method that counts how many times a character appears at the start of a string, to no avail.
Can't you just use starts_with?
fn main() {
let line_list= [ "hello", "$add", "$$add" ];
let mut result;
for line in line_list.iter() {
if line.starts_with("$$") {
result = line[1..].to_string();
}
else if line.starts_with("$") {
result = format!("operator:{}", &line[1..]);
}
else {
result = line.to_string();
}
println!("result = {}", result);
}
}
Output
result = hello
result = operator:add
result = $add
According to the comments, your problem seems to be related to the access to the first chars.
The proper and efficient way is to get a char iterator:
#[derive(Debug)]
enum Token {
Str(String),
Operator(String),
}
impl From<&str> for Token {
fn from(s: &str) -> Self {
let mut chars = s.chars();
let first_char = chars.next();
let second_char = chars.next();
match (first_char, second_char) {
(Some('$'), Some('$')) => {
Token::Str(format!("${}", chars.as_str()))
}
(Some('$'), Some(c)) => {
// your real handling here is probably different
Token::Operator(format!("{}{}", c, chars.as_str()))
}
_ => {
Token::Str(s.to_string())
}
}
}
}
fn main() {
println!("{:?}", Token::from("π"));
println!("{:?}", Token::from("hello"));
println!("{:?}", Token::from("$add"));
println!("{:?}", Token::from("$$add"));
}
Result:
Str("π")
Str("hello")
Operator("add")
Str("$add")
playground

Resources