Can Rust match struct fields? For example, this code:
struct Point {
x: bool,
y: bool,
}
let point = Point { x: false, y: true };
match point {
point.x => println!("x is true"),
point.y => println!("y is true"),
}
Should result in:
y is true
Can Rust match struct fields?
It is described in the Rust book in the "Destructuring structs" chapter.
match point {
Point { x: true, .. } => println!("x is true"),
Point { y: true, .. } => println!("y is true"),
_ => println!("something else"),
}
The syntax presented in your question doesn't make any sense; it seems that you just want to use a normal if statement:
if point.x { println!("x is true") }
if point.y { println!("y is true") }
I'd highly recommend re-reading The Rust Programming Language, specifically the chapters on
enums
match
patterns
Once you've read that, it should become clear that point.x isn't a pattern, so it cannot be used on the left side of a match arm.
Related
Doing this exercise https://github.com/rust-lang/rustlings/blob/main/exercises/option/option3.rs I discovered the ref keyword and I'm very confused when should I use & or ref in a pattern matching. In this example, every match outputs the same message, but I couldn't tell their differences:
struct Point { x: i32, y: i32}
fn main() {
let y: Option<Point> = Some(Point { x: 100, y: 200 });
match &y {
Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => println!("no match"),
}
match y {
Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => println!("no match"),
}
match &y {
Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => println!("no match"),
}
y;
}
ref is older. The fact the matching against a reference gives a reference is a result of the match ergonomics feature.
They are not exactly the same, but generally you can choose. I and many other people prefer the & form, although I saw people that do not like match ergonomics and prefer the explicit ref always.
There are case where you cannot take a reference and then you're forced to use ref. I also prefer to use ref in cases like Option::as_ref() and Option::as_mut(), where with match ergonomics they will have exactly the same code and this is confusing in my opinion, while using ref one is just ref and the other uses ref mut.
Given the macro matching example, this shows how macros can match an argument.
I've made very minor changes here to use numbers:
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(1 => 3);
}
Works, printing: mode Y: 3
However I would like to use a constant as an argument, can this be made to work:
const CONST: usize = 1;
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(CONST => 3);
}
Is this possible in Rust?
Note, using a regular match statement isn't usable for me, since in my code each branch resolves to different types, giving an error.
So I'm specifically interested to know if a constant can be passed to a macro.
No.
Macros operate on the Abstract Syntax Tree, so they reason at the syntactic level: they reason about tokens and their spelling.
For example:
fn main() {
let v = 3;
}
In this case, the AST will look something like:
fn main
\_ let-binding v
\_ literal 3
If you ask a macro whether v is 3, it will look at you funny, and wonder why you would try comparing a variable name and a literal.
I'm fairly sure the answer is "no"; at macro expansion time all you have are token trees - expansion happens before evaluation, or even type inference/checking.
const CONST: usize = 0;
macro_rules! foo {
($i:ident => $e:expr) => {
if $i == 0 {
println!("mode X: {}", $e);
} else if $i == 1 {
println!("mode Y: {}", $e);
}
};
}
fn main() {
foo!(CONST => 3);
}
If you want use identifier in macro it needs to be ident tag and you can use if, else if blocks instead of match.
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.
Given the macro matching example, this shows how macros can match an argument.
I've made very minor changes here to use numbers:
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(1 => 3);
}
Works, printing: mode Y: 3
However I would like to use a constant as an argument, can this be made to work:
const CONST: usize = 1;
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(CONST => 3);
}
Is this possible in Rust?
Note, using a regular match statement isn't usable for me, since in my code each branch resolves to different types, giving an error.
So I'm specifically interested to know if a constant can be passed to a macro.
No.
Macros operate on the Abstract Syntax Tree, so they reason at the syntactic level: they reason about tokens and their spelling.
For example:
fn main() {
let v = 3;
}
In this case, the AST will look something like:
fn main
\_ let-binding v
\_ literal 3
If you ask a macro whether v is 3, it will look at you funny, and wonder why you would try comparing a variable name and a literal.
I'm fairly sure the answer is "no"; at macro expansion time all you have are token trees - expansion happens before evaluation, or even type inference/checking.
const CONST: usize = 0;
macro_rules! foo {
($i:ident => $e:expr) => {
if $i == 0 {
println!("mode X: {}", $e);
} else if $i == 1 {
println!("mode Y: {}", $e);
}
};
}
fn main() {
foo!(CONST => 3);
}
If you want use identifier in macro it needs to be ident tag and you can use if, else if blocks instead of match.
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();