new
July 29, 2024, 3:14pm
1
I am trying to implement PartialOrd
that look like this:
#[derive(PartialEq)]
struct Dimensions {
width: f32,
height: f32,
}
impl PartialOrd for Dimensions {
fn partial_cmp(&self, right: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
match (self.width.partial_cmp(&right.width)?, self.height.partial_cmp(&right.height)?) {
(Ordering::Less, Ordering::Less) => Some(Ordering::Less),
(Ordering::Equal, Ordering::Equal) => Some(Ordering::Equal),
(Ordering::Greater, Ordering::Greater) => Some(Ordering::Greater),
_ => None,
}
}
}
And I have this macro:
macro_rules! impl_partial_ord {
[$name:ident { $($field:ident),* } ] => {
impl PartialOrd for $name {
fn partial_cmp(&self, right: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
match ( $( self.$field.partial_cmp(&right.$field)? ),* ) {
( $(Ordering::Less),* ) => Some(Ordering::Less),
// ...
_ => None,
}
}
}
};
}
impl_partial_ord! {
Dimensions { width, height }
}
Main problem is that ( $(Ordering::Less),* )
repetition contain no $field
,
And rust macro slapped in my face this this error:
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> ..\phone.rs:48:28
|
48 | ( $(Ordering::Less),* ) => Some(Ordering::Less),
| ^^^^^^^^^^^^^^^
How do I achieve similar result ? Playground
new
July 29, 2024, 3:58pm
3
Your solution require to call .partial_cmp(..)
by 3x
, Something like that:
match ( self.width.partial_cmp(&right.width)?, self.height.partial_cmp(&right.height)?, ) {
(Ordering::Less, Ordering::Less) => return Some(Ordering::Less),
_ => {},
}
match ( self.width.partial_cmp(&right.width)?, self.height.partial_cmp(&right.height)?, ) {
(Ordering::Greater, Ordering::Greater) => return Some(Ordering::Greater),
_ => {},
}
// ...
return None;
I do not want to call self.$field.partial_cmp(&right.$field)?
multiple times, for each branch,
And don't want to relay on compiler to optimize that code.
How about this?
macro_rules! impl_partial_ord {
[$name:ident { $($field:ident),* } ] => {
impl PartialOrd for $name {
fn partial_cmp(&self, right: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
$(
let Some(Ordering::Less) = self.$field.partial_cmp(&right.$field) else { return None; };
)*
Some(Ordering::Less)
}
}
};
}
new
July 29, 2024, 4:10pm
5
The output is not correct,
Output should be return
Ordering::Less
where every left
fields < every right
fields.
Ordering::Equal
where every left
fields == every right
fields.
Ordering::Greater
where every left
fields > every right
fields.
No it doesn't. What exactly prevents you from storing it in a variable?
Sorry; I didn't notice the ...
comment and just replicated the output of the macro code you posted. This should be better:
macro_rules! impl_partial_ord {
[$name:ident { } ] => {
impl PartialOrd for $name {
fn partial_cmp(&self, right: &Self) -> Option<std::cmp::Ordering> {
Some(std::cmp::Ordering::Equal)
}
}
};
[$name:ident { $first:ident $(,$field:ident)* } ] => {
impl PartialOrd for $name {
fn partial_cmp(&self, right: &Self) -> Option<std::cmp::Ordering> {
let out = self.$first.partial_cmp(&right.$first)?;
$(
if out != self.$field.partial_cmp(&right.$field)? { return None };
)*
Some(out)
}
}
};
}
I believe that @new is looking for a short-circuiting behaviour, where later fields are not compared if the result is determined by the earlier ones.
1 Like
OP's original playground suggests the opposite. It contains the following function:
fn partial_cmp(&self, right: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
match ( $( self.$field.partial_cmp(&right.$field)? ),* ) {
( $(Ordering::Less),* ) => Some(Ordering::Less),
_ => None,
}
}
which would evaluate all comparisons just as eagerly if the disallowed repetition compiled.
This doesn't evaluate any of the per-field comparisons more than once.
The general solution to this problem is to make another branch in the current macro (could also be a separate macro though) that takes $field
as input, but doesn't actually use it and instead outputs the tokens you want (Ordering::Less
in this case).
Ultimately though the flagged solution should be more efficient.
2 Likes
system
Closed
October 27, 2024, 5:54pm
11
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.