How to parameterize enumerator in rust? - enums

I'm very new to Rust and faced the following simple problem
I have the following 2 enums:
enum SourceType{
File,
Network
}
enum SourceProperties{
FileProperties {
file_path: String
},
NetworkProperties {
ip: String
}
}
Now I'd like to have HashMap<SourceType, SourceProperties>, but in such an implementation there is a potential to have mapping File -> NetworkProperties which is not what's expected.
I was thinking about to parameterize enum SourceProperties<T> with SourceType somehow, but it does not seem to be possible. Is there a way to provide such typesafety guarantees?
UPD: The intention of having the enum SourceType is that the actual SourceType is a user input which will be decoded as a String value ("File", "Network"). So the workflow would look like this
"File" -> SourceType::File -> SourceProperties::NetworkProperties

You can simple use a hash set and an enum that encapsulates the properties, for latter matching of them:
use std::collections::HashSet;
#[derive(PartialEq, Eq, Hash)]
struct FileProperties {
file_path: String
}
#[derive(PartialEq, Eq, Hash)]
struct NetworkProperties {
ip: String
}
#[derive(PartialEq, Eq, Hash)]
enum Source {
File(FileProperties),
Network(NetworkProperties)
}
fn main() {
let mut set : HashSet<Source> = HashSet::new();
set.insert(Source::File(FileProperties{file_path: "foo.bar".to_string()}));
for e in set {
match e {
Source::File(properties) => { println!("{}", properties.file_path);}
Source::Network(properties) => { println!("{}", properties.ip);}
}
}
}
Playground

Related

Should I implement a trait also by value to allow for better performance?

Let's say I have the following enums :
enum SomeEnum {
V1(i32),
V2(f32),
V3(String),
V4(Vec<i32>),
}
#[derive(Debug)]
enum SomeEnumConversionError {
OutOfTypeRange,
StringParse,
TooManyElements,
}
And I want the following trait to work for both values and references of the first enum; So I implement it for the reference to save some time and a few lines of duplicate code (Considering I have to do almost the same for all other variants and the real enum I'm working with has about 30 variants) :
impl TryFrom<&SomeEnum> for i32 {
type Error = SomeEnumConversionError;
fn try_from(value: &SomeEnum) -> Result<Self, Self::Error> {
match value {
SomeEnum::V1(v) => Ok(*v),
SomeEnum::V2(v) => {
let i = *v as i32;
if (i as f32) == *v {
Ok(i)
} else {
Err(SomeEnumConversionError::OutOfTypeRange)
}
}
SomeEnum::V3(v) => v
.parse::<i32>()
.map_err(|_| SomeEnumConversionError::StringParse),
SomeEnum::V4(v) if v.len() == 1 => Ok(v[0]),
SomeEnum::V4(v) => Err(SomeEnumConversionError::TooManyElements),
}
}
}
impl TryFrom<SomeEnum> for i32 {
type Error = SomeEnumConversionError;
fn try_from(value: SomeEnum) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
Now, if I have the following code as an example usage :
fn some_func() {
let a = SomeEnum::V4(vec![4i32]);
let b: Result<i32, _> = a.try_into();
println!("{}", b.unwrap());
}
The question is, how is this (performance-wise, considering the conversion might be called in a loop) compared to when I implement the trait for both value and reference type (rather than just calling it by reference from value implementation) ?
Does the compiler do some optimization magic here that will eventually make it behave as if the variable a has been actually moved to the function implementation calls ?
Or do I need to also implement the whole conversion function for the value type to ensure this ?
If there are better alternatives to the whole plan, I'm all ears.

Rust: take ownership of enum value without another pattern matching

The question is in the comment. Expensive is a struct that either doesn't implement Copy, or copying is too expensive.
Update: replaced Option with a user enum Internal.
enum Internal {
Type1(Expensive),
Type2(String),
Empty,
}
struct Foo {
value: Internal,
}
impl Foo {
fn exec(&mut self) -> Result<Expensive, String> {
if let Internal::Type1(_) = &self.value {
let value = std::mem::take(&mut self.value);
// QUESTION: how do I avoid this pattern matching since we know the value must be Internal::Type1
return match value {
Internal::Type1(e) => Result::Ok(e),
_ => Result::Err(String::from("Impossible")),
};
}
// Some other logic that will use self.value
}
}
You can leverage Option::take to make the code shorter:
if let Option::Some(value) = self.value.take() {
return Ok(value);
}

Rust macro generate enum from another enum

I am trying to solve a problem with code duplication to make it less error-prone.
I'm working with different Data Sources each with some specific properties. Here is how my data model looks like:
mod sources {
pub struct Ds1;
pub struct Ds2;
//hundreds more
pub trait DsTypeTrait{
type Input;
type Output;
}
impl DsTypeTrait for Ds1 {
type Input = u32;
type Output = u32;
}
impl DsTypeTrait for Ds2 {
type Input = String;
type Output = String;
}
//etc...
enum DataSource{
Ds1(Ds1),
Ds2(Ds2),
//...
}
}
So any time somebody wants to add support for a new data source they need to add it to the enum DataSource. The PROBLEM with the solution is that if another module contains some custom data format for, e.g. DataSource communications it would be required to add the DataSource in the 2 places which is extremely error-prone. For example:
mod some_other {
use super::sources::*;
struct DataSourceDataInputOutput<Ds: DsTypeTrait>{
input: <Ds as DsTypeTrait>::Input,
output: <Ds as DsTypeTrait>::Output
}
enum DataSourceIO{
Ds1(DataSourceDataInputOutput<Ds1>),
Ds2(DataSourceDataInputOutput<Ds2>)
//Extremely easy to forget to add here
}
}
QUESTION: Given the enum DataSource is it possible to write a macro to generate enum DataSourceIO automatically and avoid modifying enum DataSourceIO manually each time a new DataSource is added?
It seems enum parsing is a complicated issue so it is reasonable to move the DataSource actual declaration in the macro itself. Here is what I came up with:
#[macro_use]
mod sources {
macro_rules! data_sources {
($name:ident, $type:tt) => {
enum $name{
Ds1($type<Ds1>),
Ds2($type<Ds2>)
}
}
}
//...
}
mod some_other {
struct DataSourceDataInputOutput<Ds: DsTypeTrait>{
input: <Ds as DsTypeTrait>::Input,
output: <Ds as DsTypeTrait>::Output
}
data_sources!(Test, DataSourceDataInputOutput);
fn test_fn() {
let ds1io: DataSourceDataInputOutput<Ds1> = DataSourceDataInputOutput{
input: 1,
output: 2
};
let test = DataSourceIO::Ds1(ds1io); //works ok
}
}

How to read a value of an enum which associates with a custom type in Rust?

I have an implementation in Rust as follows. In the main function, I am reading a value in SalaryRange enum and this will display High("So High").
// This can be a complex type, just using string for the question
type SRange = String;
type SalEnu = SalaryRange<SRange>;
struct User<SRange> {
username: String,
email: String,
sign_in_count: u64,
active: bool,
income: Income<SRange>,
}
struct Income<SRange> {
salary_range: SalaryRange<SRange>
}
#[derive(Debug)]
enum SalaryRange<SRange> {
Low(SRange),
Mid(SRange),
High(SRange),
}
fn main() {
let user1 = User {
email: String::from("test#email.com"),
username: String::from("test_name"),
active: true,
sign_in_count: 1,
income: Income {
salary_range: (
SalaryRange::High("So High")
)
},
};
let mut srange: SalaryRange<&str> = user1.income.salary_range;
println!("{:?}", srange);
}
Link for this example can be found here.
Just wanted to know if there is a possibility to read and print the value in that enum as println!("{:?}", srange::High);, just to print out the string value?
I only want to print the value So High.
If I use srange::High This will throw an error saying
println!("{:?}", srange::High);
| ^^^^^^ use of undeclared type or module `srange`
error: aborting due to previous error
You can implement a method on your enum to extract the value:
#[derive(Debug)]
enum SalaryRange<S> {
Low(S),
Mid(S),
High(S),
}
impl<S> SalaryRange<S> {
fn value(&self) -> &S {
match self {
SalaryRange::Low(value) => value,
SalaryRange::Mid(value) => value,
SalaryRange::High(value) => value,
}
}
}
println!("{:?}", srange.value());
You can pattern match srange with the if let syntax.
if let SalaryRange::High(s) = srange {
println!("{}", s);
}
will print "so high".
I know it's been a while since the question has been opened, but I would like to complete Peter's answer.
There is a more idiomatic way to achieve what you want. Just implement the std::fmt::Display trait to your enum as following:
pub enum SalaryRange {
LOW(String),
MID(String),
HIGH(String),
}
impl std::fmt::Display for SalaryRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let content = match self {
SalaryRange::LOW(content) => content,
SalaryRange::MID(content) => content,
SalaryRange::HIGH(content) => content,
};
write!(f, "{}", content)
}
}
The std::fmt::Display trait allows you to display the content held by your enum value like this:
let salary_range = SalaryRange::HIGH("So high".to_string());
println!("{}", salary_range);
// outputs: "So high"
This should work with any type.
Playground to test it: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=eaea33a955dc9dcd81a4b96ec22d82bd

How to compare enum without pattern matching

I want to apply filter on an iterator and I came up with this one and it works, but it's super verbose:
.filter(|ref my_struct| match my_struct.my_enum { Unknown => false, _ => true })
I would rather write something like this:
.filter(|ref my_struct| my_struct.my_enum != Unknown)
This gives me a compile error
binary operation `!=` cannot be applied to type `MyEnum`
Is there an alternative to the verbose pattern matching? I looked for a macro but couldn't find a suitable one.
Use matches!, e.g.:
matches!(my_struct.my_enum, Unknown)
Alternatively, you can use PartialEq trait, for example, by #[derive]:
#[derive(PartialEq)]
enum MyEnum { ... }
Then your "ideal" variant will work as is. However, this requires that MyEnum's contents also implement PartialEq, which is not always possible/wanted.
I'd use pattern matching, but I'd move it to a method on the enum so that the filter closure is tidier:
#[derive(Debug)]
enum Thing {
One(i32),
Two(String),
Unknown,
}
impl Thing {
fn is_unknown(&self) -> bool {
match *self {
Thing::Unknown => true,
_ => false,
}
}
}
fn main() {
let things = [Thing::One(42), Thing::Two("hello".into()), Thing::Unknown];
for t in things.iter().filter(|s| !s.is_unknown()) {
println!("{:?}", t);
}
}
You can combine this with the matches macro as well:
fn is_unknown(&self) -> bool {
matches!(self, Thing::Unknown)
}
See also:
Compare enums only by variant, not value
You can use if let Some(x) = option { then } idiom with the same if let construct but without destructuring:
if let Unknown = my_struct.my_enum { false } else { true }

Resources