Skip to content

Allow closures to mutate their upvars #4093

Closed
@rntz

Description

@rntz

Rust does not permit the following code:

pure fn generate_nats_from(x_: uint) -> pure fn@() -> uint {
  let mut x = x_;
  || { x += 1; x }
}

It is disallowed for two reasons:

  1. You cannot implicitly capture a mutable variable, you need a capture clause.
  2. You cannot assign to captured mutable variables from heap closures.

These restrictions appear to primarily have historical motivations: originally, local variables were all mutable and were captured by copying their values at the time the closure was created.

However, it is often convenient to create a "stateful" heap closure, which maintains some hidden local state that may change between calls to it. The most natural way to do this is to allow heap closures to capture mutable variables by reference.

If we allowed this, then generate_nats_from(0) would return a function f such that the first call to f returned 1, the second 2, the third 3, etc. This is very similar to a python-style generator, albeit written explicitly instead of with yield.

To achieve this currently in rust, we must explicitly allocate a mutable managed pointer, like so:

fn generate_nats_from(x_: uint) -> fn@() -> uint {
  let x = @mut x_;
  || { *x += 1; *x }
}

Notice that I had to drop the pure annotation on the returned function, because it mutates an @-pointer. However, the function is notionally pure: it mutates nothing visible to its caller, only its own internal state.

It also makes the code uglier, and in principle less efficient, to do it this way. I say "in principle less efficient" because the code given allocates a separate GC'd box for x, when it could instead become part of the environment of the closure we return. Actually implementing this optimization, however, might be difficult.

Mostly I want this feature because it makes writing stateful closures nicer, and it makes writing pure stateful closures possible.

For examples of code that could benefit from this, see https://github.com/rntz/rust-sandbox/blob/master/stream.rs , in particular https://github.com/rntz/rust-sandbox/blob/905de30e5a7af8422ea3604b90f6443745bf6233/stream.rs#L30 and https://github.com/rntz/rust-sandbox/blob/905de30e5a7af8422ea3604b90f6443745bf6233/stream.rs#L101 .

Note in particular that the only thing keeping most of the functions in that module, and the definition of the Stream type itself, from being pure is this issue. As far as I can tell, lazy streams of this sort are doomed to second-class citizenship in the Rust purity system as long as this is not permitted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions