I have a Rust enum defined like this
enum MyFirstEnum {
TupleType(f32, i8, String),
StuctType {varone: i32, vartwo: f64},
NewTypeTuple(i32),
SomeVarName
}
I have the following code:
let mfe: MyFirstEnum = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
I'm following the Rust documentation and this looks fine. I don't need to define everything in the enum, but how would I go about accessing the mid element in the enum tuple?
mfe.TupleType.1 and mfe.1 don't work when I add them to a println!
I know Rust provides the facility to do pattern matching to obtain the value, but if I changed the code to define the other variants within the enum, the code to output a particular variant would quickly become a mess.
Is there a simple way to output the variant of the tuple (or any other variant) in the enum?
This is a common misconception: enum variants are not their own types (at least in Rust 1.9). Therefore when you create a variable like this:
let value = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
The fact that it's a specific variant is immediately "lost". You will need to pattern match to prevent accessing the enum as the wrong variant. You may prefer to use an if let statement instead of a match:
if let MyFirstEnum::TupleType(f, i, s) = value {
// Values available here
println!("f: {:?}", f);
}
Example solution:
enum MyFirstEnum {
TupleType(f32, i8, String),
// StuctType { varone: i32, vartwo: f64 },
// NewTypeTuple(i32),
// SomeVarName,
}
fn main() {
let mfe: MyFirstEnum = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
let MyFirstEnum::TupleType(value, id, text) = &mfe;
println!("[{}; {}; {}]", value, id, text);
//or
match &mfe {
MyFirstEnum::TupleType(value, id, text) => {
println!("[{}; {}; {}]", value, id, text);
}
// _ => {}
}
}
Playground link
Related
I would like to create a structure that's something like a compile-time immutable map with safely checked keys at compile-time. More generally, I would like an iterable associative array with safe key access.
My first attempt at this was using a const HashMap (such as described here) but then the keys are not safely accessible:
use phf::{phf_map};
static COUNTRIES: phf::Map<&'static str, &'static str> = phf_map! {
"US" => "United States",
"UK" => "United Kingdom",
};
COUNTRIES.get("EU") // no compile-time error
Another option I considered was using an enumerable enum with the strum crate as described here:
use strum::IntoEnumIterator; // 0.17.1
use strum_macros::EnumIter; // 0.17.1
#[derive(Debug, EnumIter)]
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
fn main() {
for direction in Direction::iter() {
println!("{:?}", direction);
}
}
This works, except that enum values in rust can only be integers. To assign a different value would require something like implementing a value() function for the enum with a match statement, (such as what's described here), however this means that any time the developer decides to append a new item, the value function must be updated as well, and rewriting the enum name in two places every time isn't ideal.
My last attempt was to use consts in an impl, like so:
struct MyType {
value: &'static str
}
impl MyType {
const ONE: MyType = MyType { value: "one" };
const TWO: MyType = MyType { value: "two" };
}
This allows single-write implementations and the objects are safely-accessible compile-time constants, however there's no way that I've found to iterate over them (as expressed by work-arounds here) (although this may be possible with some kind of procedural macro).
I'm coming from a lot of TypeScript where this kind of task is very simple:
const values = {
one: "one",
two: "two" // easy property addition
}
values.three; // COMPILE-TIME error
Object.keys(values).forEach(key => {...}) // iteration
Or even in Java where this can be done simply with enums with properties.
I'm aware this smells a bit like an XY problem, but I don't really think it's an absurd thing to ask generally for a safe, iterable, compile-time immutable constant associative array (boy is it a mouthful though). Is this pattern possible in Rust? The fact that I can't find anything on it and that it seems so difficult leads me to believe what I'm doing isn't the best practice for Rust code. In that case, what are the alternatives? If this is a bad design pattern for Rust, what would a good substitute be?
#JakubDóka How would I implement it? I did some looking at procedural macros and couldn't seem to understand how to implement such a macro.
macro_rules! decl_named_iterable_enum {
(
// notice how we format input as it should be inputted (good practice)
// here is the indentifier bound to $name variable, when we later mention it
// it will be replaced with the passed value
$name:ident {
// the `$(...)*` matches 0-infinity of consecutive `...`
// the `$(...)?` matches 0-1 of `...`
$($variant:ident $(= $repr:literal)?,)*
}
) => {
#[derive(Clone, Copy)]
enum $name {
// We use the metavar same way we bind it,
// just ommitting its token type
$($variant),*
// ^ this will insert `,` between the variants
}
impl $name {
// same story just with additional tokens
pub const VARIANTS: &[Self] = &[$(Self::$variant),*];
pub const fn name(self) -> &'static str {
match self {
$(
// see comments on other macro branches, this si a
// common way to handle optional patterns
Self::$variant => decl_named_iterable_enum!(#repr $variant $($repr)?),
)*
}
}
}
};
// this branch will match if literal is present
// in this case we just ignore the name
(#repr $name:ident $repr:literal) => {
$repr
};
// fallback for no literal provided,
// we stringify the name of variant
(#repr $name:ident) => {
stringify!($name)
};
}
// this is how you use the macro, similar to typescript
decl_named_iterable_enum! {
MyEnum {
Variant,
Short = "Long",
}
}
// some example code collecting names of variants
fn main() {
let name_list = MyEnum::VARIANTS
.iter()
.map(|v| v.name())
.collect::<Vec<_>>();
println!("{name_list:?}");
}
// Exercise for you:
// 1. replace `=` for name override with `:`
// 2. add a static `&[&str]` names accessed by `MyEnum::VARIANT_NAMES`
Rust Newbie.
I'd like to create a hashmap that contains values of different types. I got as far as shown, and I can store the values, but I cannot cast them back to the original time when reading them. I'm sure I'm missing something basic, but I'm still struggling with the enum concept in Rust.
#[derive(Debug)]
struct My1 { value: i32 }
#[derive(Debug)]
struct My2 { value: String }
#[derive(Debug)]
enum MyValueType {
MyOne(Vec<My1>),
MyTwo(Vec<My2>)
}
fn main() {
use std::collections::HashMap;
let mut map: HashMap<&str, MyValueType> = HashMap::new();
let a1 = vec!(My1 { value: 100 });
let a2 = vec!(My2 { value: "onehundred".into() });
map.insert("one", MyValueType::MyOne(a1));
map.insert("two", MyValueType::MyTwo(a2));
//let b: &Vec<My1> = map.get("one").unwrap().into(); // err
for (key, value) in &map {
println!("{}: {:?}", key, value);
}
let k1: Vec<My1> = *map.get("one").unwrap().into(); // err: type annotation needed
let k2: Vec<My2> = *map.get("two").unwrap().into(); // err: type annotation needed
}
How should I implement this so I can cast the value of type MyValueType back to Vec or Vec as the case may be? Or am I fundamentally wrong on how I'm setting this up in general?
Starting with:
let v = map.get("one");
The hash map will return an option of the enum (Option<MyValueType>). After unwrapping the option, you’re left with the enum.
let v = map.get("one"); // v is MyValueType (specifically MyOne)
This enum has one of the possible values of MyOne or MyTwo, but we don’t yet know which (more specifically — the compiler doesn’t know, even if we can tell just by looking that it’s MyOne). If you want to reach in to MyOne or MyTwo and grab one of the Vecs that are stored there, you need to match against the enum. For example:
match map.get("one").unwrap() {
MyValueType::MyOne(vector) => {
// do something
},
MyValurType::MyTwo => panic!(“unexpected)
}
This intentionally forces you to check that the enum is the value you are expecting before you are able to access to the data within. Typically you won’t know the exact type of the enum when you are writing code (otherwise why use an enum!) which is why this might seem a bit verbose.
I have a macro with a path parameter and now I want to call it through a method. However I have a hard time figuring out the parameter type of the of the method parameter that would be passed as a macro parameter.
Below is simple repro. This example use case doesn't make sense but it shows my problem clearly.
Following works:
fn main() {
let val = get_value!(true, DataValue::Int).unwrap();
println!("{}", val);
}
#[macro_export]
macro_rules! get_value {
( $rs: expr, $data_value_type: path ) => {{
let rand_val = return_data_value($rs);
if let $data_value_type(val) = rand_val {
Ok(val)
} else {
Err("This would not work")
}
}};
}
fn return_data_value(random_select: bool) -> DataValue {
if random_select {
DataValue::Int(1)
} else {
DataValue::Float(2.3)
}
}
enum DataValue {
Int(i32),
Float(f64),
String(String),
Bool(bool),
}
Now I want to create a function that calls the macro: (In the real world scenario this would be a much more complex one, but this shows the problem I have)
fn invoke_macro(random_select: bool, fun: <What type?>) -> i32 {
get_value!(random_select, fun).unwrap()
}
So here, what is the type of the parameter fun should be? Whatever I did so far gave me following error:
error: expected tuple struct or tuple variant, found local variable `fun`
label: not a tuple struct or tuple variant
Compiler tells me that the type of $data_value_type (macro parameter) for this particular invocation is fn(i32) -> DataValue {DataValue::Int} but I don't know how I can use that to figure out the parameter type.
You cannot. Not every macro can be made into a function, since macros can do syntax transformations and functions cannot.
DataValue::Int (for instance) is a function, like any tuple struct/enum variant, and that's why the compiler says this is its type. This function allows you to create the variant, like in (playground):
#[derive(Debug)]
struct S(i32);
let f: fn(i32) -> S = S;
let s = f(123);
dbg!(s);
But here, you don't use it as a function, but rather for pattern matching. This does not have a type and cannot be expressed.
Let say I define and instantiate an enum as follows:
enum MyEnum {
EmptyVariant,
TupleVariant(u8),
StructVariant {
key: u8,
value: char,
}
}
let instance = MyEnum::StructVariant{key: 8, value: 'a'};
Is it possible to match against this variant without destructuring? For example, instead of doing:
if let MyEnum::StructVariant{key, value} = instance {
eprintln!("key, value = {}, {}", key, value);
}
I would rather write something like:
if let MyEnum::StructVariant{VARIANT_MEMBERS} = instance {
eprintln!("key, value = {}, {}", VARIANT_MEMBERS.key, VARIANT_MEMBERS.value);
}
In this example, writing out the members of the struct variant is benign, but in the case where the variant has many members it makes the code difficult to read.
I don't think it is possible as of today.
I did see the following pattern, which in practice achieves what you asked for just with an extra intermediary type:
Instead place all the desired members of the StructVariant in an actual struct type, and have your enum use that struct as the only field of the StructVariant.
struct MyVariant {
key: u8,
value: char
}
enum MyEnum {
EmptyVariant,
TupleVariant(u8),
StructVariant(MyVariant)
}
if let MyEnum::StructVariant(x) = instance {
eprintln!("key, value = {}, {}", x.key, x.value);
}
This also proves handy when you want to pass the members of StructVariant around, to other functions or types.
Recently I was reading about the match keyword in the Rust Book. What confused me was the difference between Binding and Destructuring. In my understanding, both of these provide a way to access variables in an expression. Binding can specify a range matching, but you can achieve it with Destructuring and Guards. So can someone show some cases that only Binding can do or explain the real difference between these two concepts?
Here you can see one scenario when a binding is needed because destructuring doesn't satisfy our current need. If we simply destructure the struct we get access to the inner field of the struct. This means that the values used on the right hand side in the match arm won't have access to the methods defined on the struct.
In my example I also match against a specific value of ex.value, this is of course not necessary and can be done with a guard instead, this way is however more concise if the condition isn't very complex.
struct Example {
some_value: i32,
some_other_value: String
}
impl Example {
pub fn some_fn(&mut self) {}
}
fn main() {
let ex = Example { some_value: 42, some_other_value: "Foobar".to_string() };
match ex {
mut new_ex # Example { some_value: 43, .. } => new_ex.some_fn(),
Example { some_value: first, some_other_value: second } => println!("first value: {}\nSecond value: {}", first, second),
}
}