Validate enum with associated values in a vec for uniqueness - rust

Below there is code in which I have defined a method with an input type of Vec<Food>. This method should validate if an arm, without checking the associated value, must be unique. It means it should contain at most 1 pizza, 1 cake and 1 subway. Note: it is not needed that all arms are in the Vec. I wrote some tests in the code below also, they still need to pass.
I have much more enum arms in my 'real' code, and my current way doesn't scale very well, so I was hoping there is a easier way.
fn main() {
}
enum Food {
Cake(String),
Pizza(i32),
Subway(u64)
}
struct CustomError;
fn validate(foods: Vec<Food>) -> Result<(), CustomError> {
let mut cake = false;
let mut pizza = false;
let mut subway = false;
for f in foods.iter() {
match f {
Food::Cake(_) => {
if cake {
return Err(CustomError)
}
cake = true;
},
Food::Pizza(_) => {
if pizza {
return Err(CustomError)
}
pizza = true;
},
Food::Subway(_) => {
if subway {
return Err(CustomError)
}
subway = true;
},
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
assert!(validate(vec![Food::Pizza(1)]).is_ok());
assert!(validate(vec![Food::Pizza(1), Food::Cake("Apple".to_owned())]).is_ok());
assert!(validate(vec![]).is_ok());
assert!(validate(vec![Food::Pizza(1), Food::Pizza(1)]).is_err());
assert!(validate(vec![Food::Pizza(1), Food::Pizza(2)]).is_err());
}
}

To compare enum variants without caring about any associated data, the function std::mem::discriminant is very useful. Given a value of an enum type, std::mem::discriminant returns a value of type std::mem::Discriminant which tells which variant the value is. std::mem::Discriminant implements Hash, so we can keep all the variants we've seen so far in a HashSet to check if there are any duplicates.
Just a small trick: HashSet::insert returns a boolean which is true when the inserted element isn't already in the set. That means we can combine the steps of checking if a discriminant has been seen and inserting a new discriminant.
use std::collections::HashSet;
use std::mem::discriminant;
enum Food {
Cake(String),
Pizza(i32),
Subway(u64),
}
struct CustomError;
fn validate(foods: Vec<Food>) -> Result<(), CustomError> {
let mut discriminants = HashSet::new();
for food in foods {
if !discriminants.insert(discriminant(&food)) {
return Err(CustomError);
}
}
Ok(())
}
(playground)

Rust doesn't have much by way of reflection capabilities, so I suspect the best you can do is write something like a procedural macro to generate a function containing the match statement which you described not wanting to write by hand.

Related

How can I implement the typestate pattern based on a discriminator? [duplicate]

I was wondering if it was possible to return different types depending on the conditions in the function:
This code will work if you remove '|| bool' and the 'if/else' statements.
Thanks in advance.
fn main() {
let vector: Vec<i32> = vec![0, 2, 5, 8, 9];
let targetL i32 = 3;
let found_item = linear_search(vector, target);
println!("{}", &found_item);
}
fn linear_search(vector: Vec<i32>, target: i32) -> i32 || bool {
let mut found: i32 = 0;
for item in vector {
if item == target {
found = item;
break
}
}
if found == 0 {
false
} else {
found
}
}
The precise type must be known at compile time (and is subsequently erased). You cannot decide arbitrarily which types to return at runtime.
However, you can do you've tried to do, by wrapping the types into a generic enum (which replaces the || in your code):
enum TypeOr<S, T> {
Left(S),
Right(T),
}
fn linear_search(vector: ...) -> TypeOr<i32, bool> { //...
The downside is that you must unwrap the value from the enum before you can do anything else with the result. However, this isn't so arduous in practice.
This is essentially a generalised version of the commonly used Option and Result types.
Edit: In fact, in your case, you are served very nicely by the semantics of the Option type: you never return true, so you may equate the None result with the false result your function returns, and this captures the idea you're trying to express: either your linear search finds the target and returns it (Some(found)), or it does not, and has nothing to return (None).

Matching multiple possible types?

I'm very, very new to Rust and struggling with it because of my strong weakly typed programming background.
The code below should write data being received from Python via PYO3 into a XLSX worksheet. I just don't know how to handle the last match, because "value" is of type PyAny (this is, its method extract can output multiple types such as String, f32, etc. and I want a specific behavior depending on the extracted type).
Maybe I could just chain matches for each potential extracted type (if first outputs Err, try the next), but I suspect there could be a better way. Maybe I'm just approaching the problem with a wrong design. Any insights will be welcome.
pub trait WriteValue {
fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError>;
}
impl WriteValue for String {
fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError> {
worksheet.write_string(row, col, &self, format)
}
}
impl WriteValue for f32 {
fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError> {
worksheet.write_number(row, col, f64::from(*self), format)
}
}
fn _write(path: &str, data: HashMap<u32, &PyList>, _highlight: Option<&PyDict>) -> Result<(), XlsxError> {
let workbook = Workbook::new(path);
let mut worksheet = workbook.add_worksheet(None)?;
let format_bold = workbook.add_format().set_bold();
for (row_index, values) in data {
let mut col_idx: u16 = 0;
for value in values {
col_idx += 1;
let row_format= match &row_index {
0 => Some(&format_bold),
_ => None
};
match value.extract::<String>() {
Ok(x) => x.write_value(&mut worksheet, row_index.clone(), &col_idx -1, row_format)?,
Err(_) => { }
}
}
}
workbook.close()
}
This is mostly a pyo3 API issue, and I don't think pyo3 has built-in "multiextract" though I'm not ultra familiar with it, so it may.
However, first since you don't care about the Err clause you could simplify your code by simply chaining if let statements, they're syntactic sugar but for unary or binary boolean conditions they're really convenient e.g.
if let Ok(x) = value.extract::<String>() {
x.write_value(...)
} else if let Ok(x) = value.extract::<f32>() {
// handle this case and possibly add a bunch more
} else {
// handle no case matching (optional if should be ignored)
}
Second, it looks like pyo3 lets you derive enums, since WriteValue is apparently an internal trait it would make sense to derive the corresponding enum:
#[derive(FromPyObject)]
enum Writables {
#[pyo3(transparent, annotation = "str")]
String(String),
#[pyo3(transparent, annotation = "float")]
Float(f32),
// put the other cases here
}
then you can extract to that and match all the variants at once (and handle the "unsupported types" separately).
In fact at this point the trait is probably unecessary, unless it's used for other stuff, you could just have your write_value method on the enum directly.
side-note: extracting a python float (which is a double) to an f32 then immediately widening it to an f64 in order to write it out seems... odd. Why not extract an f64 in the first place?
PyAny can be try to downcast to any other Python type. I am not proficient with PyO3, but the only approach I see here is to try to downcast to the types you support otherwise maybe launch an error:
fn _write(path: &str, data: HashMap<u32, &PyList>, _highlight: Option<&PyDict>) -> Result<(), XlsxError> {
let workbook = Workbook::new(path);
let mut worksheet = workbook.add_worksheet(None)?;
let format_bold = workbook.add_format().set_bold();
for (row_index, values) in data {
let mut col_idx: u16 = 0;
for value in values {
col_idx += 1;
let row_format= match &row_index {
0 => Some(&format_bold),
_ => None
};
if let Ok(string) = value.downcast::<PyString> {
// handle pystring object
string.write_value(&mut worksheet, row_index.clone(), &col_idx -1, row_format)?;
...
} else if let Ok(int) = value.downcast::<PyInt> {
// handle pyint object
...
} else {
// error, or not supported
}
}
}
workbook.close()
}

How can I easily get a reference to a value after it has been moved into a tuple-type enum variant?

I want to move a value into a tuple-type enum variant and obtain a reference to the value after it has been moved. I see how this is possible with an if let statement, but this seems like this should be unnecessary when the particular variant is known statically.
Is there any way to get the reference to the moved value without requiring an if let or match?
This code block is a simple illustration of my question (see below for a more challenging case):
enum Transport {
Car(u32), // horsepower
Horse(String), // name
}
fn do_something(x: &String) {
println!(x);
}
fn main() {
// Can I avoid needing this if, which is clearly redundant?
if let Transport::Horse(ref name) = Transport::Horse("daisy".into()) {
do_something(name);
}
else {
// Can never happen
}
// I tried the following, it gives:
// "error[E0005]: refutable pattern in local binding: `Car(_)` not covered"
let Transport::Horse(ref name) = Transport::Horse("daisy".into());
}
It is easy to find ways to side-step the issue in the above code, since there are no real interface requirements. Consider instead the following example, where I am building a simple API for building trees (where each node can have n children). Nodes have an add_child_node method returning a reference to the node that was added, to allow chaining of calls to quickly build deep trees. (It is debatable whether this is a good API, but that is irrelevant to the question). add_child_node must return a mutable reference to the contents of an enum variant. Is the if let required in this example (without changing the API)?
struct Node {
children: Vec<Child>,
// ...
}
enum Child {
Node(Node),
Leaf
}
impl Node {
fn add_child_node(&mut self, node: Node) -> &mut Node {
self.children.push(Child::Node(node));
// It seems like this if should be unnecessary
if let Some(&mut Child::Node(ref mut x)) = self.children.last() {
return x;
}
// Required to compile, since we must return something
unreachable!();
}
fn add_child_leaf(&mut self) {
// ...
}
}
No. You can use unreachable!() for the else case, and it's usually clear even without message/comment what's going on. The compiler is also very likely to optimize the check away.
If the variants have the same type you can implement AsRef and use the Transport as a &str:
enum Transport {
Car(String),
Horse(String),
}
fn do_something<S: AsRef<str>>(x: &S) {
println!("{}", x.as_ref());
}
impl AsRef<str> for Transport {
fn as_ref(&self) -> &str {
match self {
Transport::Car(s) => s,
Transport::Horse(s) => s,
}
}
}
fn main() {
let transport = Transport::Horse("daisy".into());
do_something(&transport)
}
Playground
Otherwise you need to use a let if binding as you are doing. No need to use an else clause if you don't want to:
if let Transport::Horse(ref name) = Transport::Horse("daisy".into()) {
do_something(name);
}
define From<Transport> for String:
…
impl From<Transport> for String {
fn from(t: Transport) -> String {
match t {
Transport::Car(value) => value.to_string(),
Transport::Horse(name) => name,
}
}
}
fn do_something(x: Transport) {
println!("{}", String::from(x));
}
fn main() {
let horse = Transport::Horse("daisy".to_string());
let car = Transport::Car(150);
do_something(horse);
do_something(car);
}

rust extend built-in enum Result?

tl;dr Is it possible to extend std::result::Result to add my own variant that signals "things are Okay but also..." and keep impl Result methods like is_ok()?
I want to extend Result to signal additional states that a function caller can use for special cases.
use std::result::Result
use std::io::Error;
/// Extend Result to also signal "things are okay but check on things"
enum ResultExt<T, E> {
Result<T, E>,
OkButCheckThings(T),
}
pub fn do_stuff() -> ResultExt<u64, Error> {
// ...
}
pub fn main() -> {
let var = match do_stuff() {
Ok(val) => { val },
Err(err) => { 0 },
OkButCheckThings(val) => { check_things(); val },
}
dbg!(var);
}
It's possible to plainly extend an Enum. But I would also like to use the underlying Result<T, E> functions like is_ok.
let var2 = do_stuff();
if var2.is_ok() {
println!("It is totally Ok, nothing to check!");
}
I created a rust playground example that successfully extends Result<T, E> but the extended enum cannot use functions like is_ok().
The real-world use-case is a function that calls std::io::Read may need to "modify" the returned Result to signal additional states beyond Ok and Err. But I want these various "meta states" to be captured by one enum, as opposed to returning various other bool flags (I want to avoid return signature with (Result<T>, bool, bool). This would allow one clean match statement of all possible states; Ok, Err, "Okay but...", "Err but ...", etc..
There is no current way of "extending" and enum perse.
But it could be simply solved by embedding your own enum type into the result itself.
Simple example, similar to yours:
use std::fmt::Display;
enum StuffToCheck<T> {
Ok(T),
CheckThis(T),
}
impl<T> StuffToCheck<T>
where
T: Display + Copy,
{
pub fn check_things(&self) -> T {
match self {
Self::Ok(val) => {
*val
}
Self::CheckThis(val) => {
println!("Checking stuff for {}", val);
*val
}
}
}
}
fn do_stuff() -> ResultExt<u64> {
Ok(StuffToCheck::CheckThis(10))
}
type ResultExt<T> = Result<StuffToCheck<T>, std::io::Error>;
fn main() {
let var = match do_stuff() {
Ok(result) => result.check_things(),
Err(_err) => 0,
};
dbg!(var);
}
Playground
You could even use nested pattern matching:
...
match do_stuff() {
Err(e) => {//handle error}
Ok(StuffToCheck::Ok(value)) => { value },
Ok(StuffToCheck::CheckThis(value)) => {
check_things(value);
value
}
}
...
I think this is an instance of the X-Y problem. You can use the built-in result, you just need a different error type, that returns an option: Some(partial_result) or None.
For example you have function parse, that can attempt to adjust for a malformed input, but report the error.
pub fn parse(b: &str) -> Result<&str, CustomParseError> {
// Do something that might fail,
if failed(){
return CustomParseError::new(None)
} else if partially_failed() {
return CustomParseError::new(Some(partial_result))
} else {
return completeResult
}
}
This way you have a clean code path where nothing failed, and all of your assumptions are correct, and if it's not => instead of unwrapping, you match and check which case you have. This is vastly superior, because the error often contains enough information for you to reconstruct both what went wrong, and what could be done to fix it.

How to convert a vector of enums into a vector of inner values of a specific variant of that enum

The following code example is the best that I have come up with so far:
enum Variant {
VariantA(u64),
VariantB(f64),
}
fn main() {
let my_vec = vec![Variant::VariantA(1),
Variant::VariantB(-2.0),
Variant::VariantA(4),
Variant::VariantA(3),
Variant::VariantA(2),
Variant::VariantB(1.0)];
let my_u64_vec = my_vec
.into_iter()
.filter_map(|el| match el {
Variant::VariantA(inner) => Some(inner),
_ => None,
})
.collect::<Vec<u64>>();
println!("my_u64_vec = {:?}", my_u64_vec);
}
I would like to know if there is a less verbose way of obtaining the vector of inner values (i.e., Vec<u64> in the example). It feels like I might be able to use something like try_from or try_into to make this less verbose, but I cannot quite get there.
Enums are not "special" and don't have much if any implicitly associated magic, so by default yes you need a full match -- or at least an if let e.g.
if let Variant::VariantA(inner) = el { Some(inner) } else { None }
However nothing prevents you from implementing whatever utility methods you're thinking of on your enum e.g. get_a which would return an Option<A> (similar to Result::ok and Result::err), or indeed to implement TryFrom on it:
use std::convert::{TryFrom, TryInto};
enum Variant {
VariantA(u64),
VariantB(f64),
}
impl TryFrom<Variant> for u64 {
type Error = ();
fn try_from(value: Variant) -> Result<Self, Self::Error> {
if let Variant::VariantA(v) = value { Ok(v) } else { Err(()) }
}
}
fn main() {
let my_vec = vec![Variant::VariantA(1),
Variant::VariantB(-2.0),
Variant::VariantA(4),
Variant::VariantA(3),
Variant::VariantA(2),
Variant::VariantB(1.0)];
let my_u64_vec = my_vec
.into_iter()
.filter_map(|el| el.try_into().ok())
.collect::<Vec<u64>>();
println!("my_u64_vec = {:?}", my_u64_vec);
}

Resources