Match enum based on constructor

I have an enum and a generic function that takes a constructor for the enum and a callable that returns constructor arguments

#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
enum Snapshot {
    APath(std::path::PathBuf),
    ManyPaths(Vec<std::path::PathBuf>),
}

fn compute_or_lookup<Callable, Constructor, ConstructorArgument>(
    construct: Constructor,
    compute: Callable,
    already_done: &mut Vec<Snapshot>,
) -> ConstructorArgument
where
    Callable: FnOnce() -> ConstructorArgument,
    Constructor: FnOnce(&ConstructorArgument) -> Snapshot,
{
    match already_done
        .iter()
        .find_map(|x| match x {
            construct(argument) => Some(argument),
            _ => None,
        })
    {
        Some(x) => x,
        None => {
            let retval = compute();
            already_done.push(construct(&retval));
            retval
        }
    }
}

i.e. I have a compute intense path that calls the callable, constructs an enum with the callable's return, and inserts it into the vector. As a shortcut I want to check if that operation has already been done. (We can assume that each enum variant will only be constructed once).

The above example however does not compile because

  --> src/main.rs:21:13
   |
21 |             construct(argument) => Some(argument),
   |             ^^^^^^^^^ not a tuple struct or tuple variant


Is there a way i can match on "the enum that gets returned by the given constructor"?

EDIT:

as a usage example, I would want to call

fn some_function() -> std::path::PathBuf ....

fn main() {
    let mut some_vector : Vec<Snapshot> = ...;
    let _ = compute_or_lookup(Snapshot::APath, some_function, &some_vector);
}

i.e. in the match i would want to evaluate something like

match x {
    Snapshot::APath(p) => Some(p),
    _ => None
}

or

match x {
    Snapshot::ManyPaths(v) => Some(v),
    _ => None
}

just the Snapshot::ManyPaths or Snapshot::APath replaced by the at-code-write-time-unknown constructor.

You match on the return value.

match some_func_call(args) {
    TheReturnedEnum::Variant(..) => { .. }
    // ..
}

If you mean, is there some way to know if a function/closure always returns the same enum variant -- no, there isn't. That would be "pattern types" or "enum variants are types" or such, which Rust doesn't have so far.

I'm somewhat guessing at what you want here, because your example doesn't make sense for reasons other than the immediate error. What are you trying to do with x in find_map? It's a &Snapshot and you don't have any closures that take a &Snapshot. Where did argument come from? Also calling construct would give you a Snapshot, but then you try to return it (Some(x) => x), even though this function returns a ConstructorArgument. Etc.

2 Likes

If I understand, you can't match like that, so you need separate args for the matcher and the constructor. Below is what I think you're trying to do, but it fails because the newly computed value is moved into the vec but also returned by the function. You can't do both, or you have to clone it.

fn compute_or_lookup<Matcher, Construct, Compute, Argument>(
    matcher: Matcher,
    compute: Compute,
    construct: Construct,
    already_done: &mut Vec<Snapshot>,
) -> Argument
where
    Matcher: FnMut(&Snapshot) -> Option<Argument>,
    Compute: FnOnce() -> Argument,
    Construct: FnOnce(Argument) -> Snapshot,
{
    match already_done.iter().find_map(matcher) {
        Some(x) => x,
        None => {
            let retval = compute();
            already_done.push(construct(retval));
            retval
        }
    }
}

Playground

1 Like

When putting construct(argument) in pattern position, you're asking for the argument value given the return value of a function, which isn't computationally feasible, since functions are one-way transformations. And Rust doesn't have a way to pass patterns as arguments, which are one-way in the other direction. You can decide whether or not to enter this match arm with discriminant, but there doesn't appear to be a way to create your ConstructorArgument from x.

1 Like

The original avoids this by making construct take a reference.

already_done.push(construct(&retval));
1 Like

You're right! Here's the fixed playground.

I guess there is cloning in matcher and construct.

1 Like

if you want a thing that can produce a pattern, your only option in current rust is to use a macro.

if you do it right, you might actually be able to use your macro in the pattern position and in a value position!

however, macros can't do much other than shuffling items around and providing defaults, at least until patterns can contain const expressions

1 Like

Thanks for looking into it and sorry for being unclear, hope the edit clears things up a bit. argument is my attempt do to pattern matching like in Check if a vector of enums contains any enum of a type regardless the value - #2 by mwillsey .

I can't tell from your response whether you understood this: You can't match on a constructor function passed as a parameter. So you can't do what you're trying to do, exactly.

I posted an alternative approach that requires passing one additional closure.

I see … does it make a difference that, my constructor won't just be any constructor, I would only use enum variants spelled out? i.e. Snapshot::APath? (cf. the edit in the original post) So the constructor would always "look like" the things i would type in a working pattern match expression. (if not, i can use your suggestion with the additional matcher closure, though i'm also a bit nerd-sniped giving @binarycat 's suggestion a try and use a macro)

No, that wouldn't matter. Parameters passed to functions are evaluated before calling the function, so the constructor is passed as a function pointer. And you can't match on a function pointer.

In Rust (and most programming languages) the way to pass literal syntax through as a parameter, like you're wanting to do, would require writing a macro, not a function. So you could write a macro, but I don't recommend it unless you're feeling comfortable with Rust in general and have a desire to learn how to write macros.


https://doc.rust-lang.org/book/ch19-06-macros.html

The downside to implementing a macro instead of a function is that macro definitions are more complex than function definitions because you’re writing Rust code that writes Rust code. Due to this indirection, macro definitions are generally more difficult to read, understand, and maintain than function definitions.

1 Like