What is feature "bind_by_move_pattern_guards"?

I just revisited some code I wrote many weeks ago that has been running 24/7 since and hence entirely forgotten about. It was one of my first Rust programs, a simple thing that reads a serial port and publishes the bytes to a NATS server.

Anyway, I could not build it with stable because it complained about "#![feature(bind_by_move_pattern_guards)]". Now, I don't recall putting any such thing in my code, no doubt I naively cut and pasted it from some example as a Rust newbie, and I have no idea what it does. So I deleted it and my code built with stable without it. So far so good.

Googling around I find it's described here: https://rust-lang.github.io/rfcs/0107-pattern-guards-with-bind-by-move.html

That floored me as I did not know matches could have guards and "move-bound variable" is not a term I have heard before.

Could somebody explain in language a C/C++ refugee what that is all about? Is it even something I need to think about moving forward or just another little Rust nicety?

Basically the match is being used in a way that takes ownership of the matched-upon value, which means that the variables in the patterns would own the value if the case is taken. The issue is that when evaluating the if guard on the match, you have to use the variable by reference instead of by value, since if you return false, some other guard needs to own the value.

Thanks, I think I kinda'sorta get the idea. I have a real hard time parsing paragraphs like that though. Not to worry, I'll take a few runs at it.

Sounds to me like something we need not think about when it makes it into stable. Things will "just work" where they might not have otherwise.

Yeah, this is a "make things just work when they should just work" thing.

First of all, you need to know what a match actually does, see this post of mine: How to understand the match pattern?

Now, Rust being Rust, ownership comes into question. Imagine the following counter-example:

fn main ()
{
    let opt_v = Some(vec![42]);
    match opt_v {
        | Some(v)
            if v.into_iter().next().is_none() // <-- pattern guard
        => {
            // use v?
            drop(v)
        },
        | Some(v) => {
            // use v?
            drop(v)
        },
        | None => {},
    }
}

So the issue lies mainly at the pattern guard line: if such code were to compile, then the guard would have taken and consumed ownership of v, meaning that it would be impossible to use v within its body.

Worse, if the check failed (but having already taken ownership of v), then the following match branches would be using a moved value.

That's why

A pattern guard is not allowed to move out of a variable bound by the pattern it guards.

This means that the v within the pattern guard is more like *(&v)1; so when v : impl !Copy, unless you go and use & v ~ & *(&v) ~ &v, you get a compiler error.

The fact that v behaves as *(&v) is described as: "v has been bound by reference" (rather than by move).


Now, up until Rust version 1.39.0, for technical reasons / due to a compiler limitation, this behavior of "v is bound by reference" / v behaves as *(&v) was not supported. Instead, you were forced to make such "by ref" binding explicit by annotating the binding with ref, which resulted in a bind-by-ref, even within the arm body, resulting in an unneeded and unjustified coding constraint.

So what currently can be written as:

match Some(vec![42]) {
    | Some(v) if v.is_empty() => {
        // owns v: Vec<i32>
        drop::<Vec<_>>(v);
    },
    | _ => {},
}

had to be written as:

match Some(vec![42]) {
    | Some(ref v) if v.is_empty() => {
        // because of `ref`, `v` is a borrow: `v: &Vec<_>`.
    },
    | _ => {},
}

which means that code like the following one would fail to compile:

match vec![42] {
    | v if true => drop(v),
    | _ => {},
}

Conclusion

Since 1.39.0, we can bind-by-move a pattern variable in the body of a match arm guarded by a condition.
As @alice TL,DR-ed: now

it just works™


1 Rust seems to have made the sensible but opinionated choice of not using *(&mut v): Playground

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.