Matching with less boilerplate - rust

I have nested structures of enums, ie. enum X has a variant which contains enum Y etc. Is there any way to access fields eg x.y.z with a runtime error if x.y.z doesn't exist (eg, x is of wrong variant). Furthermore is this a reasonably moral thing to do, or is there a better alternative, without match statements everywhere? Efficiency is not so important, though it would be good to make it as cheap as possible.

It is not possible to access nested enums with dots syntax, but you can use if let:
pub enum X {
A(Y),
B(Z)
}
pub enum Y {
C,
D(u32)
}
pub enum Z {
E
}
fn main() {
let x = X::A(Y::D(12));
if let X::A(Y::D(n)) = x {
println!("Got it: {}", n);
} else {
println!("Nope");
}
}
(try it here)
if let makes the code arguably more concise than match. Naturally, this is as efficient as match.

Given these definitions:
enum X {
Variant(Y),
…
}
enum Y {
Variant(Z),
…
}
struct Z;
You can use if let with deep patterns as one way:
if let X::Variant(Y::Variant(ref z) = x {
// Use z
} else { // And these two lines are,
panic!(); // of course, optional.
}
Or you could define methods (panicking is not a good idea, though; having such methods return Option is much more common, as is shown in rustc_serialize’s Json enum, for example), but it will still leave you with comparatively ugly code, probably):
impl X {
fn y(&self) -> &Y {
match *self {
Variant(ref y) => y,
_ => panic!(),
}
}
}
// Ditto for Y.z()
let z = x.y().z();

Related

Recursive closure inside a function [duplicate]

This is a very simple example, but how would I do something similar to:
let fact = |x: u32| {
match x {
0 => 1,
_ => x * fact(x - 1),
}
};
I know that this specific example can be easily done with iteration, but I'm wondering if it's possible to make a recursive function in Rust for more complicated things (such as traversing trees) or if I'm required to use my own stack instead.
There are a few ways to do this.
You can put closures into a struct and pass this struct to the closure. You can even define structs inline in a function:
fn main() {
struct Fact<'s> { f: &'s dyn Fn(&Fact, u32) -> u32 }
let fact = Fact {
f: &|fact, x| if x == 0 {1} else {x * (fact.f)(fact, x - 1)}
};
println!("{}", (fact.f)(&fact, 5));
}
This gets around the problem of having an infinite type (a function that takes itself as an argument) and the problem that fact isn't yet defined inside the closure itself when one writes let fact = |x| {...} and so one can't refer to it there.
Another option is to just write a recursive function as a fn item, which can also be defined inline in a function:
fn main() {
fn fact(x: u32) -> u32 { if x == 0 {1} else {x * fact(x - 1)} }
println!("{}", fact(5));
}
This works fine if you don't need to capture anything from the environment.
One more option is to use the fn item solution but explicitly pass the args/environment you want.
fn main() {
struct FactEnv { base_case: u32 }
fn fact(env: &FactEnv, x: u32) -> u32 {
if x == 0 {env.base_case} else {x * fact(env, x - 1)}
}
let env = FactEnv { base_case: 1 };
println!("{}", fact(&env, 5));
}
All of these work with Rust 1.17 and have probably worked since version 0.6. The fn's defined inside fns are no different to those defined at the top level, except they are only accessible within the fn they are defined inside.
As of Rust 1.62 (July 2022), there's still no direct way to recurse in a closure. As the other answers have pointed out, you need at least a bit of indirection, like passing the closure to itself as an argument, or moving it into a cell after creating it. These things can work, but in my opinion they're kind of gross, and they're definitely hard for Rust beginners to follow. If you want to use recursion but you have to have a closure, for example because you need something that implements FnOnce() to use with thread::spawn, then I think the cleanest approach is to use a regular fn function for the recursive part and to wrap it in a non-recursive closure that captures the environment. Here's an example:
let x = 5;
let fact = || {
fn helper(arg: u64) -> u64 {
match arg {
0 => 1,
_ => arg * helper(arg - 1),
}
}
helper(x)
};
assert_eq!(120, fact());
Here's a really ugly and verbose solution I came up with:
use std::{
cell::RefCell,
rc::{Rc, Weak},
};
fn main() {
let weak_holder: Rc<RefCell<Weak<dyn Fn(u32) -> u32>>> =
Rc::new(RefCell::new(Weak::<fn(u32) -> u32>::new()));
let weak_holder2 = weak_holder.clone();
let fact: Rc<dyn Fn(u32) -> u32> = Rc::new(move |x| {
let fact = weak_holder2.borrow().upgrade().unwrap();
if x == 0 {
1
} else {
x * fact(x - 1)
}
});
weak_holder.replace(Rc::downgrade(&fact));
println!("{}", fact(5)); // prints "120"
println!("{}", fact(6)); // prints "720"
}
The advantages of this are that you call the function with the expected signature (no extra arguments needed), it's a closure that can capture variables (by move), it doesn't require defining any new structs, and the closure can be returned from the function or otherwise stored in a place that outlives the scope where it was created (as an Rc<Fn...>) and it still works.
Closure is just a struct with additional contexts. Therefore, you can do this to achieve recursion (suppose you want to do factorial with recursive mutable sum):
#[derive(Default)]
struct Fact {
ans: i32,
}
impl Fact {
fn call(&mut self, n: i32) -> i32 {
if n == 0 {
self.ans = 1;
return 1;
}
self.call(n - 1);
self.ans *= n;
self.ans
}
}
To use this struct, just:
let mut fact = Fact::default();
let ans = fact.call(5);

Match on pair of enum when both are of the same kind

I want to get rid of the repeated code here, and not have to list each enum kind:
use Geometry::*;
let geometry = match (&self.geometry, &other.geometry) {
(Point(a), Point(b)) => Point(*a.interpolate(b, t)),
(Curve(a), Curve(b)) => Curve(*a.interpolate(b, t)),
(EPBox(a), EPBox(b)) => EPBox(*a.interpolate(b, t)),
(_, _) => unimplemented!("Kinds not matching")
};
The types inside the kinds all implement the Interpolate trait which has the interpolate method:
enum Geometry {
Point(Point),
Curve(Curve),
EPBox(EPBox)
}
trait Interpolate {
fn interpolate(&self, other: &Self, t: f64) -> Box<Self> {
// ...
}
}
impl Interpolate for Point {}
impl Interpolate for Curve {}
impl Interpolate for EPBox {}
What I want to do is something like this:
let geometry = match (&self.geometry, &other.geometry) {
(x(a), x(b)) => x(*a.interpolate(b, t)), // and check that a and b impls Interpolate
(_, _) => unimplemented!("Kinds not matching")
};
I'm not sure how many of these operations you want to implement, and how many enum variants you actually have. Depending on that, the best answer changes quite a bit, I think. If you really only have one operation (interpolate), and three variants: type it out, anything else will be a lot less simple and maintainable.
That being said, I might use
let geometry = binop!(match (&self.geometry, &other.geometry) {
(a, b) => *a.interpolate(b, t)
});
based on
macro_rules! binop {
(match ($av:expr, $bv:expr) { ($a:ident, $b:ident) => $op:expr }) => {{
use Geometry::*;
binop!($av, $bv, $a, $b, $op, Point, Curve, EPBox)
}};
($av:expr, $bv:expr, $a:ident, $b:ident, $op:expr, $($var:path),*) => {
match ($av, $bv) {
$(($var($a), $var($b)) => $var($op),)*
_ => unimplemented!("Kinds not matching")
}
};
}
Playground
You'll have to list up the enum variants once. If you don't want that, you'll have to go for a proc macro. I think that would be overkill.
With your current code, you can't really do this. Checking if the enum variants are the same variant is fairly simple with the use of core::mem::discriminant, but you can't access the contained value without match or if let statements. What you can do is use type parameters for the variables and type IDs. This is a really tacky way to do things, and should be avoided really, but it works. I've given an example to show you how you could implement it
use std::any::Any;
trait SomeTrait {
fn some_function(&self, other: Self) -> &Self;
}
#[derive(Clone)]
struct Var0 {
eg: i64
}
#[derive(Clone)]
struct Var1 {
other_eg: f64
}
fn check<T: 'static, B: 'static>(lhs: T, rhs: B) -> Option<T> {
if lhs.type_id() == rhs.type_id() {
Some(lhs.some_function(rhs))
} else {
None
}
}
fn main() {
let a = Var0{ eg: 2 };
let b = Var0{ eg: 9 };
let c = Var1 { other_eg: -3f64 };
assert!(check(a.clone(), b).is_some());
assert!(check(a, c).is_none());
}
impl SomeTrait for Var0 { /* ... */ }
impl SomeTrait for Var1 { /* ... */ }
Please note though: as I said before this is messy and pretty hacky and not very nice to read. If I were writing this I would use the match statement over this, but it's up to you what you do since this does require some reworking to implement. I'm also not sure if you can use different lifetimes for the check function other than `static which might be a problem.
I came up with the following solution, which is easy to extend with new Interpolate types:
macro_rules! geometries {
( $( $x:ident ),+ ) => {
enum Geometry {
$(
$x($x),
)*
}
fn interpolate_geometries(a: &Geometry, b: &Geometry, t: f64) -> Geometry {
use Geometry::*;
match (a, b) {
$(
($x(a), $x(b)) => $x(a.interpolate(b, t)),
)*
_ => unimplemented!("Kinds not matching")
}
}
$(
impl Interpolate for $x {}
)*
};
}
geometries!(Point, Curve, EPBox);
Which make it possible to later do:
let geometry = interpolate_geometries(&self.geometry, &other.geometry, t);
Thanks for all answers and comments! Especially the one with the binop macro, which is quite similar to my solution. After reading up on macros (thanks to comments here) I came up with this solution.

Validate enum with associated values in a vec for uniqueness

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.

Is there a way to use custom patterns such as a regex or functions in a match?

I'm writing a toy programming language in Rust. I prototyped the parser logic in Ruby:
def rd_tree(chars)
loop do
case c = chars.next
when /\s/
# whitespace stuff
when "("
# open paren stuff
when ")"
# close paren stuff
else
# default stuff
end
end
end
And now I'm converting it to Rust:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
if c.is_whitespace() {
// whitespace stuff
} else if c == '(' {
// open paren stuff
} else if c == ')' {
// close paren stuff
} else {
// default stuff
}
}
}
I resorted to using an if, else-if chain because as far as I can tell, Rust's match feature is limited to destructuring, enums, and type patterns. Is there a way to match on regexes or boolean functions? If not, is there a more idiomatic pattern here than if, else-if? I expect the logic to have more branches in the future and I want it to stay neat.
Not yet. The match patterns must be composed of things that can be statically verified by the compiler.
However, you can use a match guard:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
match c {
c if c.is_whitespace() => {}
'(' => {}
')' => {}
_ => {}
}
}
}
A match guard allows you to run a function against whatever the pattern matched.
In the future, constant evaluation may be improved to allow calling functions in place of a pattern:
#[derive(PartialEq, Eq)]
struct Foo {
f: usize,
g: usize,
}
impl Foo {
const fn repeated(x: usize) -> Self {
Foo { f: x, g: x }
}
}
fn main() {
let f = Foo { f: 0, g: 1 };
match f {
const { Foo::repeated(22) } => println!("hi"),
_ => println!("1"),
}
}
This work is tracked in issue #57240. RFC 2920 "const expressions and patterns" (and its tracking issue #76001) are also relevant.
It's not immediately obvious to me how this would work with your exact example or a regex without a substantial amount of effort though.

Can I combine variable assignent with an if?

I have this code:
let fd = libc::creat(path, FILE_MODE);
if fd < 0 {
/* error */
}
the equivalent in C is shorter:
if ((fd = creat(path, FILE_MODE)) < 0) {
/* error */
}
can I do a similar thing in Rust? I tried to map it to if let but it looks like handling Options.
No, it's not possible by design. let bindings are one of the two non-expression statements in Rust. That means that the binding does not return any value that could be used further.
Bindings as expressions don't make a whole lot of sense in Rust in general. Consider let s = String::new(): this expression can't return String, because s owns the string. Or what about let (x, _) = get_tuple()? Would the expression return the whole tuple or just the not-ignored elements? So ⇒ let bindings aren't expressions.
About the if let: Sadly that won't work either. It just enables us to test if a destructuring works or to put it in other words: destructure a refutable pattern. This doesn't only work with Option<T>, but with all types.
If you really want to shorten this code, there is a way: make c_int easily convertible into a more idiomatic type, like Result. This is best done via extension trait:
trait LibcIntExt {
fn to_res(self) -> Result<u32, u32>;
}
impl LibcIntExt for c_int {
fn to_res(self) -> Result<u32, u32> {
if self < 0 {
Err(-self as u32)
} else {
Ok(self as u32)
}
}
}
With this you can use if let in the resulting Result:
if let Err(fd) = libc::creat(path, FILE_MODE).to_res() {
/* error */
}

Resources