@@ -549,6 +549,139 @@ Notably, it breaks swapping two array elements::
549
549
release(newArrayBuffer_j)
550
550
release(newArrayBuffer_i)
551
551
552
+ get- and setForMutation
553
+ ~~~~~~~~~~~~~~~~~~~~~~~
554
+
555
+ Some collections need finer-grained control over the entire mutation
556
+ process. For instance, to support divide-and-conquer algorithms using
557
+ slices, sliceable collections must "pin" and "unpin" their buffers
558
+ while a slice is being mutation to grant permission for the slice
559
+ to mutate the collection in-place while sharing ownership. This
560
+ flexibility can be exposed by a pair of accessors that are called
561
+ before and after a mutation. The "get" stage produces both the
562
+ value to mutate, and a state value (whose type must be declared) to
563
+ forward to the "set" stage. A pinning accessor can then look something
564
+ like this::
565
+
566
+ extension Array {
567
+ subscript(range: Range<Int>) -> Slice<Element> {
568
+ // `getForMutation` must declare its return value, a pair of both
569
+ // the value to mutate and a state value that is passed to
570
+ // `setForMutation`.
571
+ getForMutation() -> (Slice<Element>, PinToken) {
572
+ let slice = _makeSlice(range)
573
+ let pinToken = _pin()
574
+ return (slice, pinToken)
575
+ }
576
+
577
+ // `setForMutation` receives two arguments--the result of the
578
+ // mutation to write back, and the state value returned by
579
+ // `getForMutation`.
580
+ setForMutation(slice, pinToken) {
581
+ _unpin(pinToken)
582
+ _writeSlice(slice, backToRange: range)
583
+ }
584
+ }
585
+ }
586
+
587
+ ``getForMutation `` and ``setForMutation `` must appear as a pair;
588
+ neither one is valid on its own.
589
+ When the compiler has visibility that storage is implemented in
590
+ terms of ``getForMutation `` and ``setForMutation ``, it lowers a mutable
591
+ projection using those accessors as follows::
592
+
593
+ // A mutation like this (assume `reverse` is a mutating method):
594
+ array[0...99].reverse()
595
+ // Decomposes to:
596
+ let index = 0...99
597
+ (var slice, let state) = array.`subscript.getForMutation`(index)
598
+ slice.reverse()
599
+ array.`subscript.setForMutation`(index, slice, state)
600
+
601
+ To support the conservative access pattern,
602
+ a `materializeForSet ` accessor can be generated from `getForMutation `
603
+ and `setForMutation ` in an obvious fashion: perform `getForMutation `
604
+ and store the state result in its scratch space, and return a
605
+ callback that loads the state and hands it off to `setForMutation `.
606
+
607
+ The beacon of hope for a user-friendly future: Inversion of control
608
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
609
+
610
+ Addressors and ``{get,set}ForMutation `` expose important optimizations
611
+ to the standard library, but are undeniably fiddly and unsafe constructs
612
+ to expose to users. A more natural model would be to
613
+ recognize that a compound mutation is a composition of nested scopes, and
614
+ express it in the language that way. A strawman model might look something
615
+ like this::
616
+
617
+ var foo: T {
618
+ get { return getValue() }
619
+ set { setValue(newValue) }
620
+
621
+ // Perform a full in-out mutation. The `next` continuation is of
622
+ // type `(inout T) -> ()` and must be called exactly once
623
+ // with the value to hand off to the nested mutation operation.
624
+ mutate(next) {
625
+ var value = getValue()
626
+ next(&value)
627
+ setValue(value)
628
+ }
629
+ }
630
+
631
+ This presents a natural model for expressing the lifetime extension concerns
632
+ of addressors, and the state maintenance necessary for pinning ``getForMutation ``
633
+ accessors::
634
+
635
+ // An addressing mutator
636
+ mutate(next) {
637
+ withUnsafePointer(&resource) {
638
+ next(&$0.memory)
639
+ }
640
+ }
641
+
642
+ // A pinning mutator
643
+ mutate(next) {
644
+ var slice = makeSlice()
645
+ let token = pin()
646
+ next(&slice)
647
+ unpin(token)
648
+ writeBackSlice(slice)
649
+ }
650
+
651
+ For various semantic and implementation efficiency reasons, we don't want to
652
+ literally implement every access as a nesting of closures like this. Doing so
653
+ would allow for semantic surprises (a mutate() operation never invoking its
654
+ continuation, or doing so multiple times would be disastrous), and would
655
+ interfere with the ability for `inout ` and `mutating ` functions to throw or
656
+ otherwise nonlocally exit. However, we could present this model using
657
+ *inversion of control *, similar to Python generators or async-await.
658
+ A `mutate ` operation could `yield ` the `inout ` reference to its inner value,
659
+ and the compiler could enforce that a `yield ` occurs exactly once on every
660
+ control flow path::
661
+
662
+ // An addressing, yielding mutator
663
+ mutate {
664
+ withUnsafePointer(&resource) {
665
+ yield &$0.memory
666
+ }
667
+ }
668
+
669
+ // A pinning mutator
670
+ mutate {
671
+ var slice = makeSlice()
672
+ let token = pin()
673
+ yield &slice
674
+ unpin(token)
675
+ writeBackSlice(slice)
676
+ }
677
+
678
+ This obviously requires more implementation infrastructure than we currently
679
+ have, and raises language and library design issues (in particular,
680
+ lifetime-extending combinators like ``withUnsafePointer `` would need either
681
+ a ``reyields `` kind of decoration, or to become macros), but represents a
682
+ promising path toward exposing the full power of the accessor model to
683
+ users in an elegant way.
684
+
552
685
Acceptability
553
686
-------------
554
687
0 commit comments