Implementing inclusion test robust to optionals using macro

Hi, I'm fairly new to rust, trying to get used to new concepts.

I tried to write a macro that can check if a given value is equal to one of the literals.
It would be rather a simple task using position, but I wanted seamless support for Option.
Say, I'd like to write the following code,

let x = None
let ls = vec![Some(1), None, Some(2), Some(3)]
assert!(ls.iter().position(|&y| y == x).is_none())

without having to write Some so many times.

This is the closest I could get, yet fails.

macro_rules! oneof {
    ($x :expr ;; $y:expr) => (lifting_eq!($x, $y));
    ($x :expr ;; $y:expr, $($z :expr),+) => ( lifting_eq!($x, $y) || oneof!($x ;; $($z),*) );
}

macro_rules! lifting_eq { ($a :expr, $b :expr) => ({
    use std::convert::TryInto;
    let a :Result<Option<_>,_> = $a.try_into();
    let b :Result<Option<_>,_> = $b.try_into();
    a == b
})}

fn main() {
    let c :Option<u32> = None;
    assert!(!oneof!(4u32 ;; 1u32, 2u32, 3u32));
    assert!( oneof!(1u32 ;; 1u32, 2u32, 3u32));
    assert!(!oneof!(1u32 ;; None, 2u32, 3u32));
    //assert!( oneof!(None ;; None, 2u32, 3u32));
    //assert!( oneof!(c ;; None, 2u32, 3u32));
}

The last two lines of main do not compile with the error cannot infer type for a in lifting_eq macro invocation.

error[E0282]: type annotations needed
  --> ps.rs:45:12
   |
45 |     let a :Result<Option<_>,_> = $a.try_into();
   |            ^^^^^^^^^^^^^^^^^^^ cannot infer type
...
57 |     assert!( oneof!(c;; 1u32, None, 3u32));
   |              ---------------------------- in this macro invocation
   |
   = note: this error originates in the macro `lifting_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

Is there a way to work around this error? or any other suggestion to implement it?

You basically don't need the try_into() at all. That's what confuses the compiler.

into() and try_into() aren't magic, so the compiler doesn't assume transitivity. If T: Into<U> and U: Into<W>, it doesn't mean that T: Into<W> and in particular you can't get from T to W in a single .into() invocation.

Furthermore, because Into (and TryInto) are parametric over the result type (as opposed to it being an associated type), the choice of which specific result type to convert to is ambiguous. Therefore, a chain of multiple .into()s and try_into()s generally doesn't compile, as there's no information for the compiler to know what type to call the method on, beyond the first method call.

Accordingly, you only need this:

macro_rules! oneof {
    ($v:expr; $($items:expr),* $(,)?) => {{
        let v = Option::from($v);
        let items = [$(Option::from($items)),*];
        items.iter().any(|x| *x == v)
    }}
}

That was quick! Thank you very much, it was really helpful.

By the way, I was expecting some kind of conditional macro implementation on argument types,
match typeof!($v) { Option<_> => ..., _ => ..., } like I could do in zig. I got stuck in the idea, missing the rust-way solution utilizing differing implementations for From trait of Option. Just in curious, I wonder is there a way to do it zig-way in rust? And also, which do you consider a better approach from a language design perspective (cons & pros)?

Rust macro has no type information available, it operates purely on syntactical tokens.

2 Likes

If you want to work with types, you'll need to use traits and generics. The macro solution is simpler in this case, because you can't e.g. mix types in a collection, but it's possible to implement this using type-level recursion, by creating compile-time heterogeneous lists out of (head, (tail, …)) pairs:

trait List<V> {
    type Tail: List<V>;
    
    fn into_head_tail(self) -> Option<(V, Self::Tail)>;
}

impl<V> List<V> for () {
    type Tail = ();
    
    fn into_head_tail(self) -> Option<(V, Self::Tail)> {
        None
    }
}

impl<T, U, V> List<Option<V>> for (T, U)
where
    T: Into<Option<V>>,
    U: List<Option<V>>,
{
    type Tail = U;
    
    fn into_head_tail(self) -> Option<(Option<V>, Self::Tail)> {
        let (head, tail) = self;
        Some((head.into(), tail))
    }
}

fn one_of<V, L>(value: impl Into<Option<V>>, list: L) -> bool
where
    V: PartialEq,
    L: List<Option<V>>,
{
    let value = value.into();
    list.into_head_tail().map_or(false, |(head, tail)| {
        head == value || one_of(value, tail)
    })
}

fn main() {
    let c: Option<u32> = None;
    assert!(!one_of(4u32, (1u32, (2u32, (3u32, ())))));
    assert!( one_of(1u32, (1u32, (2u32, (3u32, ())))));
    assert!(!one_of(1u32, (None, (2u32, (3u32, ())))));
    assert!( one_of(None, (None, (2u32, (3u32, ())))));
    assert!( one_of(c, (None, (2u32, (3u32, ())))));
}
2 Likes

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.