Conditions in a match arm without destructuring - rust

use std::cmp::Ordering;
fn cmp(a: i32, b: i32) -> Ordering {
match {
_ if a < b => Ordering::Less,
_ if a > b => Ordering::Greater,
_ => Ordering::Equal,
}
}
fn main() {
let x = 5;
let y = 10;
println!("{}", match cmp(x, y) {
Ordering::Less => "less",
Ordering::Greater => "greater",
Ordering::Equal => "equal",
});
}
How to use match with conditions, without destructuring (because there's nothing to destructure), in the function cmp above?
The code has been adapted from the well-known example in the book which uses only if/else, however, it does not work:
src/main.rs:5:9: 5:10 error: unexpected token: `_`
src/main.rs:5 _ if a < b => Ordering::Less,
^
Could not compile `match_ordering`.
I am using rustc 1.0.0-nightly (3ef8ff1f8 2015-02-12 00:38:24 +0000).
This would work:
fn cmp(a: i32, b: i32) -> Ordering {
match (a, b) {
(a,b) if a < b => Ordering::Less,
(a,b) if a > b => Ordering::Greater,
_ => Ordering::Equal,
}
}
but it would use destructuring. Is there any other way, or is this just the most idiomatic, clean way of writing it?

You need to match on something, i.e. match { ... } isn't valid because there needs to be something between the match and the {. Since you don't care about the value itself, matching on unit, (), should be fine: match () { ... }, e.g.:
match () {
_ if a < b => Ordering::Less,
_ if a > b => Ordering::Greater,
_ => Ordering::Equal
}
(To be strict: match { _ => ... } is actually trying to parse the { ... } as the match head (i.e. the expression being matched on), and _ isn't valid at the start of an expression, hence the error.)
On idiomacity: I personally think expressing a series of conditions with short results (like this) is fine with match and a series of _ if ...s as you have done.

Related

Parse eof or a character in nom

I'm parsing with the nom library.
I would like to match on something that is followed by an 'end', without consuming it.
A end for me is either eof or a char that satisfies a function f: Fn(char) -> bool.
I can use f with nom::character::complete::satisfy, nom::bytes::take_while, or many other nom functions.
The incompatabilites between eof and f makes it impossible for me to compose, with nom combinators or my own combinators.
This is illegal, because of opaque type:
fn end(i: &str) -> IResult<&str, &str> {
match eof(i) {
o # Ok(_) => o,
e # Err(_) => peek(satisfy(f)),
}
}
This is also illegal:
alt((eof, satisfy(f)))
I can't even make this work:
alt((eof, char(' ')))
I can't even make THIS work: (because match arms has incompatible types??)
fn end(i: &str) -> IResult<&str, ()> {
match eof(i) {
o # Ok((sur, res)) => Ok((i, ())),
e # Err(_) => match satisfy(f) {
Ok(_) => Ok((i, ())),
e # Err(_) => e,
},
}
}
I made it work by providing an error myself:
fn end(i: &str) -> IResult<&str, ()> {
match eof::<&str, ()>(i) {
o # Ok((_, _)) => Ok((i, ())),
e # Err(_) => match satisfy::<_, _, ()>(is_url_terminative)(i) {
Ok(_) => Ok((i, ())),
Err(_) => Err(nom::Err::Error(Error {
input: i,
code: nom::error::ErrorKind::Permutation,
})),
},
}
}

Referring to matched value in Rust

Suppose I have this code:
fn non_zero_rand() -> i32 {
let x = rand();
match x {
0 => 1,
_ => x,
}
}
Is there a concise way to put the rand() in the match, and then bind it to a value. E.g. something like this:
fn non_zero_rand() -> i32 {
match let x = rand() {
0 => 1,
_ => x,
}
}
Or maybe:
fn non_zero_rand() -> i32 {
match rand() {
0 => 1,
_x => _x,
}
}
A match arm that consists of just an identifier will match any value, declare a variable named as the identifier, and move the value to the variable. For example:
match rand() {
0 => 1,
x => x * 2,
}
A more general way to create a variable and match it is using the # pattern:
match rand() {
0 => 1,
x # _ => x * 2,
}
In this case it is not necessary, but it can come useful when dealing with conditional matches such as ranges:
match code {
None => Empty,
Some(ascii # 0..=127) => Ascii(ascii as u8),
Some(latin1 # 160..=255) => Latin1(latin1 as u8),
_ => Invalid
}
You can bind the pattern to a name:
fn non_zero_rand() -> i32 {
match rand() {
0 => 1, // 0 is a refutable pattern so it only matches when it fits.
x => x, // the pattern is x here,
// which is non refutable, so it matches on everything
// which wasn't matched already before
}
}

Capture the entire contiguous matched input with nom

I'm looking to apply a series of nom parsers and return the complete &str that matches. I want to match strings of the form a+bc+. Using the existing chain! macro I can get pretty close:
named!(aaabccc <&[u8], &str>,
map_res!(
chain!(
a: take_while!(is_a) ~
tag!("b") ~
take_while!(is_c) ,
|| {a}
),
from_utf8
));
where
fn is_a(l: u8) -> bool {
match l {
b'a' => true,
_ => false,
}
}
fn is_c(l: u8) -> bool {
match l {
b'c' => true,
_ => false,
}
}
Say we have 'aaabccc' as input. The above parser will match the input but only 'aaa' will be returned. What I would like to do is return 'aaabccc', the original input.
chain! is not the right macro for this, but there was not another that seemed more correct. What would the best way to do this be?
At the time of this writing I'm using nom 1.2.2 and rustc 1.9.0-nightly (a1e29daf1 2016-03-25).
It appears as if you want recognized!:
if the child parser was successful, return the consumed input as produced value
And an example:
#[macro_use]
extern crate nom;
use nom::IResult;
fn main() {
assert_eq!(aaabccc(b"aaabcccddd"), IResult::Done(&b"ddd"[..], "aaabccc"));
}
named!(aaabccc <&[u8], &str>,
map_res!(
recognize!(
chain!(
take_while!(is_a) ~
tag!("b") ~
take_while!(is_c),
|| {}
)
),
std::str::from_utf8
)
);
fn is_a(l: u8) -> bool {
match l {
b'a' => true,
_ => false,
}
}
fn is_c(l: u8) -> bool {
match l {
b'c' => true,
_ => false,
}
}
I'm not sure if chain! is the best way of combining sequential parsers if you don't care for the values, but it works in this case.

A simple formula interpreter

To understand Rust, I am trying to implement a little formula interpreter.
An expression can only be an integer, a sum, a variable (Term) or an assignment (Set). We can then evaluate an expression. Since symbols with no associated values can appear in an expression, its evaluation yields another expression (and not necessarily an integer).
The values of the variables (if there are any) can be found in a hash table.
use std::rc::Rc;
use std::collections::HashMap;
enum Expr {
Integer(i32),
Term(String),
Plus(Rc<Expr>, Rc<Expr>),
Set(Rc<Expr>, Rc<Expr>),
}
impl Expr {
fn evaluate(&self, env: &mut HashMap<String, Expr>) -> Expr {
match *self {
Expr::Plus(ref a, ref b) => {
let a_ev = Rc::new(a.evaluate(env));
let b_ev = Rc::new(b.evaluate(env));
match (*a_ev, *b_ev) {
(Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x + y),
_ => Expr::Plus(a_ev, b_ev),
}
}
Expr::Term(ref a) => *env.get(&a).unwrap(),
Expr::Set(ref a, ref b) => {
let b_ev = Rc::new(b.evaluate(env));
match **a {
Expr::Term(x) => {
let x_value = env.get_mut(&x).unwrap();
*x_value = *b_ev;
*b_ev
}
otherwise => {
let a_ev = Rc::new(a.evaluate(env));
Expr::Set(a_ev, b_ev)
}
}
}
otherwise => otherwise,
}
}
}
The above code does not compile. Each match seems to borrow a variable. Moreover, I think we should not use the String type, but I can't understand why.
The compilation error:
error[E0277]: the trait bound `std::string::String: std::borrow::Borrow<&std::string::String>` is not satisfied
--> src/main.rs:22:39
|
22 | Expr::Term(ref a) => *env.get(&a).unwrap(),
| ^^^ the trait `std::borrow::Borrow<&std::string::String>` is not implemented for `std::string::String`
|
= help: the following implementations were found:
<std::string::String as std::borrow::Borrow<str>>
This question is somewhat subjective, but here are some problems I see:
let a_ev = Rc::new(a.evaluate(env));
let b_ev = Rc::new(b.evaluate(env));
match (*a_ev, *b_ev) {
(Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x+y),
_ => Expr::Plus(a_ev,b_ev)
}
Here, you can't dereference a_ev and b_ev because *a_ev is owned by the Rc container holding it. You can fix this error by waiting until you actually need the values to be put in Rc containers to create them:
match (a.evaluate(env), b.evaluate(env)) {
(Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x + y),
(a_ev, b_ev) => Expr::Plus(Rc::new(a_ev), Rc::new(b_ev))
}
Expr::Term(ref a) => *env.get(&a).unwrap()
Here, the variable a has type &String, and so writing &a makes no sense -- it would be like a reference to a reference. That can be fixed by changing &a to a. Also, env.get(a).unwrap() is a reference to a Expr that is owned by the HashMap, so you can't dereference/move it. One solution to this problem would be to use a HashMap<String, Rc<Expr>> instead of a HashMap<String, Expr>. Another would be to simply clone the value:
Expr::Term(ref a) => env.get(a).unwrap().clone(),
In order to be able to clone the value, you must use a "derive" compiler directive to say that Expr implements that trait:
#[derive(Clone)]
enum Expr { // ...
let b_ev = Rc::new(b.evaluate(env));
match **a {
Expr::Term(x) => {
let x_value = env.get_mut(&x).unwrap();
*x_value = *b_ev;
*b_ev
},
// ...
Here, you move *b_ev into the HashMap and then try to dereference/move it again by returning it. Also, like above, you have an extra &. Both of these issues can be solved in the same way as above:
let b_ev = b.evaluate(env);
match **a {
Expr::Term(ref x) => {
let x_value = env.get_mut(x).unwrap();
*x_value = b_ev.clone();
b_ev
},
// ...
otherwise => { let a_ev = Rc::new(a.evaluate(env)); // ...
Here, you are moving **a into otherwise while it is still owned by an Rc container. Since you don't use otherwise, the problem is easily fixed by replacing it with _:
_ => { // ...
otherwise => otherwise
You can't take a value that is owned by something else (*self is owned by something else) and return it by value. You can, however, clone it:
_ => self.clone()
Overall, the problem with your code is that it tries to duplicate data in a few places. As I said above, there are two ways of fixing it that I can think of: using Rc<Expr> everywhere instead of Expr, or using clone. Here is a fixed version of your code that compiles and uses clone:
use std::rc::Rc;
use std::collections::HashMap;
#[derive(Clone, Debug)]
enum Expr {
Integer(i32),
Term(String),
Plus(Rc<Expr>, Rc<Expr>),
Set(Rc<Expr>, Rc<Expr>),
}
impl Expr {
fn evaluate(&self, env: &mut HashMap<String, Expr>) -> Expr {
match *self {
Expr::Plus(ref a, ref b) => {
match (a.evaluate(env), b.evaluate(env)) {
(Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x + y),
(a_ev, b_ev) => Expr::Plus(Rc::new(a_ev), Rc::new(b_ev))
}
},
Expr::Term(ref a) => env.get(a).unwrap().clone(),
Expr::Set(ref a, ref b) => {
let b_ev = b.evaluate(env);
match **a {
Expr::Term(ref x) => {
let x_value = env.get_mut(x).unwrap();
*x_value = b_ev.clone();
b_ev
},
_ => {
let a_ev = a.evaluate(env);
Expr::Set(Rc::new(a_ev), Rc::new(b_ev))
}
}
}
_ => self.clone()
}
}
}
fn main() {
let e = Expr::Plus(Rc::new(Expr::Integer(9)), Rc::new(Expr::Integer(34)));
let mut env = HashMap::new();
println!("{:?}", e.evaluate(&mut env));
}
[playpen]
This is a second version where I follow Adrian's suggestion to replace all ExprNode with Rc<ExprNode>. The only cloned variables are Rc pointers, so I guess this just increments a reference count. My only regret is that we lost the method syntax, but I think this can be repaired by defining Expr with a struct instead of a type alias.
use std::rc::Rc;
use std::collections::HashMap;
#[derive(Debug)]
enum ExprNode {
Integer(i32),
Term(String),
Plus(Expr, Expr),
Set(Expr, Expr),
}
type Expr = Rc<ExprNode>;
type Env = HashMap<String, Expr>;
fn evaluate(e: &Expr, env: &mut Env) -> Expr {
match **e {
ExprNode::Plus(ref a, ref b) => {
let a_ev = evaluate(a, env);
let b_ev = evaluate(b, env);
match (&*a_ev, &*b_ev) {
(&ExprNode::Integer(x), &ExprNode::Integer(y)) => Rc::new(ExprNode::Integer(x + y)),
_ => Rc::new(ExprNode::Plus(a_ev, b_ev)),
}
}
ExprNode::Term(ref a) => env.get(a).unwrap().clone(),
ExprNode::Set(ref a, ref b) => {
let b_ev = evaluate(b, env);
match **a {
ExprNode::Term(ref x) => {
let x_value = env.get_mut(x).unwrap();
*x_value = b_ev;
x_value.clone()
}
_ => Rc::new(ExprNode::Set(evaluate(a, env), b_ev)),
}
}
_ => e.clone(),
}
}
fn main() {
let e = Rc::new(ExprNode::Plus(
Rc::new(ExprNode::Integer(9)),
Rc::new(ExprNode::Integer(4)),
));
let mut env = HashMap::new();
println!("{:?}", evaluate(&e, &mut env));
}

Can you put another match clause in a match arm?

Can you put another match clause in one of the match results of a match like this in:
pub fn is_it_file(input_file: &str) -> String {
let path3 = Path::new(input_file);
match path3.is_file() {
true => "File!".to_string(),
false => match path3.is_dir() {
true => "Dir!".to_string(),
_ => "Don't care",
}
}
}
If not why ?
Yes you can (see Qantas' answer). But Rust often has prettier ways to do what you want. You can do multiple matches at once by using tuples.
pub fn is_it_file(input_file: &str) -> String {
let path3 = Path::new(input_file);
match (path3.is_file(), path3.is_dir()) {
(true, false) => "File!",
(false, true) => "Dir!",
_ => "Neither or Both... bug?",
}.to_string()
}
Sure you can, match is an expression:
fn main() {
fn foo() -> i8 {
let a = true;
let b = false;
match a {
true => match b {
true => 1,
false => 2
},
false => 3
}
}
println!("{}", foo()); // 2
}
You can view the results of this on the Rust playpen.
The only thing that seems off about your code to me is the inconsistent usage of .to_string() in your code, the last match case doesn't have that.

Resources