Warm tip: This article is reproduced from stackoverflow.com, please click
rust unsafe

Conditionally capture variables in closure to implement custom control

发布于 2020-04-07 23:18:39

I'm trying to implement custom control structures in Rust. For example, suppose that for whatever reason, I want to implement a custom if statement as a function. The function would take a condition, a closure representing the true branch and a closure representing the false branch. Depending on the condition, I would call the true branch closure or the false branch closure.

It looks something like this:

pub fn fun_if<O>(
    cond: bool,
    mut tbranch: impl FnMut() -> O,
    mut fbranch: impl FnMut() -> O,
) -> O {
    if cond {
        tbranch()
    } else {
        fbranch()
    }
}

The problem with this implementation is that the true closure can not mutably borrow the same variables as the false closure:

let mut test = 0;
fun_if(true, || test = 1, || test = 2)
                ^^^^         ^^^^ ! error !

However, the rust if statement is smart enough to know that the true and false branch will never be called together. The following compiles just fine:

let mut test = 0;
if true {
    test = 1
} else {
    test = 2
}

My question is if the behavior of if is possible to replicate with functions and unsafe code in rust.

(This is a slightly contrived example and I'm happy to provide the real example where this came up if anybody is interested. What I asked here is the core idea though.)

Questioner
Sam Thomas
Viewed
69
L. Riemer 2020-02-02 04:26

In addition to Stargateurs link, which is absolutely correct in the general case, I want to point out that this specific problem could be easily solved with macros. Here goes my take at the problem.

Macro

macro_rules! macro_if {
    ($cond:expr, $t:stmt, $f:stmt) => {
        if ($cond) {
            $t
        } else {
            $f
        }
    };
}

Example usage

let return_val = macro_if!(
        !cond,
        {
            test = 1;
            0
        },
        {
            test = 2;
            5
        }
    );

As macros are evaluated at compile-time, this solution enables your use-case, while still preserving the rust compilers ability to reason about your code, safely. While using unsafe in this simple example migth be justifiable and correct, more complicated situations may quickly lead to unsoundness bugs in your implementation. We don't want that to happen.

The downside of this approach is that it probably breaks your auto-completion inside the macro invocation. At least for me, macros don't harmonize with the rls that well (yet).