<div dir="ltr"><div>Hello, swift-evolution</div><div><br></div><div>This is a big feature, and I don&#39;t expect it to get into Swift 3.0, even though it may require ABI changes. But still, I find it important enough to put it on the table for discussion.</div><div><br></div><div>Code examples below are quite sketchy - just some Swift-like pseudocode to express the idea, rather then an actual proposal of changes to the language syntax.</div><div><br></div><div>Basic idea is to associate each variable with a lock that guards it though a mechanism of lock names. Variable can be read only if associated lock is held for reading or writing. Variable can be written only if associated lock is held for writing. Locks are mutexes, read-write locks and also special locks that cover cases of thread-contained variables and immutable data.</div><div><br></div><div>class Foo&lt;Lk : LockName&gt; {</div><div>    var bar under Lk : Int = 0</div><div>}</div><div><br></div><div>func add&lt;DstLk : LockName, SrcLk : LockName&gt;(dst : Foo&lt;DstLk&gt;, src : Foo&lt;SrcLk&gt;, srcMutex : ReadWriteLock&lt;SrcLk&gt;) write DstLk {</div><div>    // Held locks: [(write DstLk)]</div><div>    dst.bar += src.bar // Error: cannot read src.bar because SrcLk is not held</div><div>    sync read srcMutex {</div><div>        // Held locks: [(write DstLk), (read SrcLk)]</div><div>        dst.bar += src.bar // Ok</div><div>    }</div><div>    // Held locks: [(write DstLk)]</div><div>}</div><div><br></div><div>Lock names are generic parameters of classes and functions and kinds are used to distinguish between type parameters and lock name parameters. Ideally lock names should not require witness tables or any other runtime otherhead, but there are few open questions related to dynamic downcasting.</div><div><br></div><div>Function signature includes lock requirements - in the example above, function add() can be called only if lock DstLk is known to be held for writing at the call site.</div><div>For every statement inside the function, compiler tracks a set of locks currently being held. Initially this set is initialized with lock requirements, and sync statement adds one more lock to this set for the scope of it&#39;s body.</div><div><br></div><div>Constructing new locks returns a dependent tuple:</div><div><br></div><div>func newMutex() -&gt; (L : LockName, Mutex&lt;L&gt;)</div><div><br></div><div>Unpacking that tuple allows to create objects protected by newly created mutex:</div><div><br></div><div>let (Lk, m) = newMutex()</div><div>let f = Foo&lt;Lk&gt;()</div><div><br></div><div>Lock names can be assigned only to constants. Each call of the newMutex() returns distinct lock name. If two generic types contain lock names and compiler cannot prove that these lock names are equal, then such types are considered to be distinct:</div><div><br></div><div>let (Lk1, m1) = newMutex()</div><div>let f1 = Foo&lt;Lk1&gt;()</div><div>let (Lk2, m2) = newMutex()</div><div>let f2 = Foo&lt;Lk2&gt;()</div><div>let Lk3 : LockName = Lk1</div><div>var f3 : Foo&lt;Lk3&gt; = f1 // Ok, Lk3 == Lk1 =&gt; Foo&lt;Lk3&gt; == Foo&lt;Lk1&gt;</div><div>f3 = f2 // Error, Lk3 != Lk2 =&gt; Foo&lt;Lk3&gt; != Foo&lt;Lk2&gt;</div><div><br></div><div>Fields of structs and enums are always protected by the same lock that protects the entire struct/enum and that lock is not a part of a variable type, but rather an attribute of a variable itself. Classes may use different locks to protect their fields.</div><div><br></div><div>There is a special kind of lock names - ThreadName, a subkind of LockName. Each function has at least one lock name of kind ThreadName for which it requires write access. This is a special lock name used for thread-contained objects. There is no mutex and any other real lock associated with this name, but write access is granted at thread entry point. Function main() has signature:</div><div><br></div><div>func main&lt;ThreadLk : LockName&gt;() write ThreadLk</div><div><br></div><div>And API for creating a new thread takes a first-class generic function of type:</div><div><br></div><div>typealias ThreadFunc =
 &lt;ThreadLk : LockName&gt;() write ThreadLk -&gt; Void</div><div><br></div><div>If there is no explicit lock name of this kind for which write access is requested, that implicit generic argument called ThisThread is added to the function:</div><div><br></div><div>func main() ==&gt; func main&lt;ThisThread : ThreadName&gt;() write ThreadName</div><div><br></div><div>If there is one, then ThisThread is defined as an alias to that argument.</div><div><br></div><div>func main&lt;ThreadLk : LockName&gt;() write ThreadLk { ... }</div><div>==&gt;</div><div>func main&lt;ThreadLk : LockName&gt;() write ThreadLk {</div><div><span class="" style="white-space:pre">        </span>let ThisThread = ThreadLk</div><div>    ...</div><div>}</div><div><br></div><div>If there are multiple such locks, then all them are considered equal.</div><div><br></div><div>Inside the closure, there is also a ThisThread lock name, which shadows the definition of the ThisThread from the parent scope. These ThisThread&#39;s are not equal.</div><div><br></div><div>A small demo of the described above:</div><div><br></div><div>class RunLoop&lt;Lk : ThreadLock&gt; {</div><div>    typealias BlockType = () write Lk -&gt; Void</div><div>    let MutexLock : LockName</div><div>    let mutex : Mutex&lt;MutexLock&gt;</div><div>    let cond : WaitCondition&lt;MutexLock&gt;</div><div>    var queue under MutexLock : [BlockType]</div><div>    var stopped under MutexLock : Bool = false</div><div>    </div><div>    init() {</div><div>        // Lock is not required to be held to initialize a variable</div><div>        (MutexLock, mutex) = newMutex()</div><div>        cond = WaitCondition&lt;MutexLock&gt;()</div><div>    }</div><div>    </div><div>    func run() write Lk {</div><div>        while let block = takeBlock() {</div><div>            block();</div><div>        }</div><div>    }</div><div>    </div><div>    func stop() {</div><div>        sync write mutex {</div><div>            stopped = true</div><div>        }</div><div>    }</div><div>    </div><div>    func enqueue(block : BlockType) { // No lock requirements - can be called from any thread </div><div>        sync write mutex {</div><div>            queue += [block]</div><div>            cond.signal()</div><div>        }</div><div>    }</div><div>    </div><div>    func takeBlock() -&gt; BlockType? {</div><div>        sync write mutex {</div><div>            while (queue.isEmpty) {</div><div>                if stopped {</div><div>                    return nil</div><div>                }</div><div>                cond.wait()</div><div>            }</div><div>            let retVal = queue[0]</div><div>            queue.removeAtIndex(0)</div><div>            return retVal</div><div>        }</div><div>    }</div><div>}</div><div><br></div><div><br></div><div>/// Thread-local variable</div><div>thread var _currentRunLoop : RunLoop&lt;ThisThread&gt;? = nil</div><div><br></div><div>func currentRunLoop() -&gt; RunLoop&lt;ThisThread&gt; {</div><div>    if let loop = _currentRunLoop {</div><div>        return loop</div><div>    } else {</div><div>        let loop = RunLoop&lt;ThisThread&gt;()</div><div>        _currentRunLoop = loop</div><div>        return loop</div><div>    }</div><div>}</div><div><br></div><div>struct Rect {</div><div>    var x : Int</div><div>    var y : Int</div><div>    var width : Int</div><div>    var height : Int</div><div>}</div><div><br></div><div>class View&lt;Lk : LockName&gt; {</div><div>    var frame under Lk : Rect</div><div>    var subviews under Lk : [View&lt;Lk&gt;]</div><div>}</div><div><br></div><div>func foo(subviews : [View&lt;ThisThread&gt;]) {}</div><div><br></div><div>func main() -&gt; Void {</div><div>    let loop = currentRunLoop()</div><div>    let view = View&lt;ThisThread&gt;()</div><div>    let Lk : ThreadName = ThisThread</div><div>    spawn({ // Starts new thread</div><div>        var sum = 0</div><div>        for x in 1..&lt;1000 {</div><div>            sum += x</div><div>        }</div><div>        loop.enqueue({</div><div>            () write Lk in</div><div>            view.frame.x = sum</div><div>            foo(view.subviews)</div><div>        })</div><div>    })</div><div>    loop.run()</div><div>}</div><div><br></div><div>Immutable variables can be protected by the special lock name constant - Immutable. Read lock for this lock name is always implicitly assumed to be held. But write lock cannot be acquired, because it is not possible to create a mutex or read-write lock associated with this name.</div><div><br></div><div>Atomic types are classes whose methods don&#39;t have any lock requirements, and thus can be called from any thread. They are reference types, because identity is important for atomic variables.</div><div><br></div><div>Described type system is based on this paper - <a href="https://homes.cs.washington.edu/~djg/papers/cycthreads.pdf">https://homes.cs.washington.edu/~djg/papers/cycthreads.pdf</a>, with some modifications:</div><div>* Lock name for the current thread is not a constant, but an argument to every function. So, sharabilities are not needed. Also sharabilities prevent passing references to thread-local objects to other threads without dereferencing them - this is a very common thing for callbacks accessing UI objects.</div><div>* Read and Write locks are distinguished, and Immutable lock is added</div><div><br></div><div>There are some known limitations:</div><div>1. Lock-less migration between threads is not supported</div><div><br></div><div>class Foo {</div><div>    var x : Int = 0</div><div>    fun inc() { x += 1 }</div><div>}</div><div><br></div><div>func main() {</div><div>    let f = Foo()</div><div>    f.inc()</div><div>    let t = spawn({</div><div>       f.inc()</div><div>    })</div><div>    t.join()</div><div>    f.inc()</div><div>}</div><div><br></div><div>Either lock needs to be added, or deep copy should be made.</div><div><br></div><div>2. Mutation after initialization is not supported:</div><div><br></div><div>class Foo&lt;L : LockName&gt; {</div><div>    var bar under L : Int</div><div><br></div><div>    init(x : Int) {</div><div>        self.bar = x</div><div>        self.bar += 1 &lt;— ERROR</div><div>    }</div><div>}</div><div><br></div><div>Either calculations need to be done in local variables before initialization, or lock requirement needs to be added.</div><div><br></div><div>3. And the most disturbing one - is subclass has lock names not mentioned into base class or protocol, then dynamic downcast is a problem:</div><div><br></div><div>class Foo&lt;A : LockName&gt; {</div><div>}</div><div><br></div><div>class Bar&lt;A : LockName, B : LockName&gt; : Foo&lt;A&gt; {</div><div>    func update() write B { … }</div><div>}</div><div><br></div><div>func process&lt;Lk : LockName&gt;(foo : Foo&lt;Lk&gt;) {</div><div>    if let bar = foo as? Bar&lt;Lk, ThisThread&gt; {</div><div>        bar.update()</div><div>    }</div><div>}</div><div><br></div><div>This can be implemented by storing lock names in runtime and actually checking them during the cast, but this seems to me to be too expensive.</div><div><br></div><div>Alternatives are:</div><div>- Forbidding such downcasts</div><div>- using existential subcasting:</div><div><br></div><div>class Foo&lt;A : LockName&gt; {</div><div>}</div><div><br></div><div>class Bar&lt;A : LockName, B : LockName&gt; : Foo&lt;A&gt; {</div><div>    func update() write A { … }</div><div>}</div><div><br></div><div>func process&lt;Lk : LockName&gt;(foo : Foo&lt;Lk&gt;) {</div><div>    if let (bar, B_Lk) = foo as? Bar&lt;Lk, _&gt; {</div><div>        bar.update() &lt;— ERROR</div><div>    }</div><div>}</div><div><br></div><div>But usefulness of obtained lock name is doubtful - it can be used only if associated lock is stored inside the object itself.</div></div>