Explicit Captures for Closures and Code Blocks in Rust

In his second video on a games programming language, Jonathan Blow discusses the concept of C++ captures in its lambda syntax, and the potential generalization of the capture syntax to any block. I think that this feature would be useful to implement in Rust for the following reasons:

  1. Additional type safety with low compile-time overhead: if the programmer explicitly states the namespace of the closure, the compiler has an easier job of labeling everything in that namespace as moved/borrowed/etc. depending on the information the user provided.
  2. Additional information for the compiler: the programmer can use the syntax to explicitly state that the code block doesn’t modify global state, i.e. its a pure function, without requiring the compiler to do any analysis beside namespace checking.
  3. Faster testing and code factoring: Jon states in his video that using this syntax could make it easier to prototype the movement of a code block out of a larger function and into a smaller utility function. This is even more true of Rust; we already have block expressions, so adding a syntax to limit the namespace of a block would just make it easier to move blocks into their own function.
  4. Readability without comments: the syntax would give future readers additional information about the function and its side effects, in a way that can be validated by the compiler. i.e. we can have a compile-time guarrantee that certain functions do not and cannot change global state.

The syntax could be implemented using a similar syntax to that discussed in the video:

pub fn long_monolithic_function(state: GlobalState) -> GlobalState {
    // Here we're using a capture in square brackets
    // to say that we only want to operate on 2 members of the input struct,
    // with mutation on one, and rename them for easier processing
    let mut result: u64 = [&state.data as data, &mut state.other as other] {
        other += 1;

        // We use the empty brackets to state that global/non-local state
        // isn't used here, and its a pure function
        data.iter().map(|item| [] {
           // Complicated stuff here
           // Lots of logic
        })
        .sum()
    }
    // do work with result here
    // ...
    // ...
    state
}

pub fn my_function(foo: String) -> bool [&bar] {
    // do work on foo, while reading the state of bar
    // ...
    bar.validate(foo)
}

There are a few potential downsides to this idea:

  1. Additional complexity of the language: the language by definition becomes more complex.
  2. Worse compilation times in the general case: its hard to say for certain, but certainly if nobody uses the feature the compiler will just be objectively slower.
  3. Questionable necessity: the existing syntax and borrow-checking system might sufficient to give the guarantees that this proposal aims to provide. For the person writing the code this feature is absolutely unnecessary; the borrow-checker already does the checks to see if your closure/block does things that aren’t memory safe. Something like this would be objectively useful in a language with less static analysis, but here its use case may already be covered.
  4. Unclear syntax: it’s unclear how the syntax should actually look. The above version of the syntax is not binding by any means.
  5. Unclear implementation: this idea might require lost of changes to the way that namespaces are currently handled internally.