Exclusive scopes that ban access to variables in scope

Context

Swift programmers can currently use closures to embed a snippet of swift code with local variables whose scope does not allow access from the closure's parent scope. Of course, this allows programmers to conveniently reuse variable names in closures, as it limits the scope of local variables to only the portion of code in which they are needed.

Consider a feature which works in a somewhat opposite way; instead of limiting the outer scope from accessing the inner scope, we limit the inner scope by explicitly declaring which names are forbidden in the inner scope.

Potential Benefits

  • Limiting scope can protect the programmer from referencing names which are not relevant to subsequent lines of code. This provides a sort of "assertion" that future modifications to the code will not depend on the state of explicitly listed outside variables.
  • Incentivizes grouping related single-use-case code into sections of more specific behavior
  • Provides an alternative to simply using a function declared outside of the existing scope (e.g. you can get the proposed behavior by writing a function and passing all relevant parameters, but this would mean pulling out code that may be specific to the scope of the function in a way that confuses collaborators who see the newly extracted code as a potentially reusable snippet of behavior).

Potential syntax

func loadViews() {
    let newView1 = { <self.view> // potential syntax for indicating that self.view should not be accessible in this embedded closure
        // ... code which is not allowed to access self.view
    }()

    let newView2 = { <self.view> // potential syntax for indicating that self.view should not be accessible in this embedded closure
        // ... code which is not allowed to access self.view
    }()

    self.stackSubview(newView1)
    self.stackSubview(newView2)
}

Relevant Inspiration

In my implementation of an algorithm which should not be split into multiple functions, I found myself at risk of accidentally using local variables with similar names that are relevant to most of, but not all of the body of the function. A medium-sized clutter of variable names put me at risk of catastrophically using a variable, and I wished that there had been some sort of assertion to ensure that I or another programmer would never make that mistake.

Consequences of the change

This change does not have to affect compiler performance with low-level checks on access legality; it could simply serve as a safety check at the semantic analysis stage. I haven't fully developed this idea or alternative ways to syntactically support this feature, so any input is appreciated.

Isn’t the requirement for explicit capture of self what protects you against this?

1 Like

I guess it won't take long for someone to brush away the whole idea, so I'll better hurry :smiley:

Angle brackets imho are not a good choice — I'd stick with what we have for capture lists, and try to extend that syntax, e.g.

let newView1 = { [^self.view] … // Don't use self.view
let newView1 = { [@strict] … // Don't allow any implicit capturing

Probably, there are better options for sigils or names, but I guess it's unavoidable that it's at least as cryptic as the current capture lists...

2 Likes

FYI, it’s pretty-much impossible to enforce this. Anybody could create an extension returning an object which captures the one you tried to prohibit. How is the compiler supposed to know which things capture what?

C# 9 will have a feature similar to this: static lambdas. Rather than attempting to specify a list of disallowed captures it just lets you specify that the lambda can //only// reference static fields/properties/methods. That is, it can't capture any locals (including this). I think this would be a simpler feature to understand and use. You can, of course, pass in any values you want to access within the closure as arguments.

2 Likes

Could we go the other way. For any function that is not in the scope of another function, all the local names need to be unique relative to the current scope. Two sibling nested functions can reuse names, because they'll never be active at the same time. But otherwise, a nested function can't reuse the names from its enclosing function (which can't reuse the names from its nesting function, etc.)..

Technically, you can achieve this in Swift using @convention(c), although it’s not really intended as a type system constraint per se.

1 Like