[swift-evolution] [Manifesto] Ownership

plx plxswift at icloud.com
Tue Mar 7 10:47:24 CST 2017


> On Feb 27, 2017, at 11:08 AM, John McCall <rjmccall at apple.com> wrote:
> 
>> On Feb 25, 2017, at 11:41 AM, plx via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 1: Collection Composition
>> 
>> As much as the high-level picture is focused on safety, I’d just like to request a strong focus on making sure the eventual ownership system addresses the common issues like avoiding copies when mutating through optionals, or when nesting collections, and so on.
> 
> Avoiding copies is basically the entire core of the proposal.  The interaction with safety is that sometimes copies are necessary to avoid memory unsafety, and this proposal recommends an approach that minimizes that.

Thanks for the extend reply and sorry for my own belated reply.

What motivated me to ask for more focus on the common cases was that it was unclear from reading the proposal whether the `inout` binding--as used in `for inout`--could be used with `if`: in other words, we currently have `if let` and `if var`, but will we have `if inout` under this proposal?

I am embarassed to admit I honestly couldn't tell if the proposal didn't contain any examples of `if inout` due to (a) the capability following trivially from the rest of the proposal or (b) the capability falling outside the scope of the proposal.

If it's (a) I apologize and feel free to skip down to the next section, if it's (b) I'll reiterate my request to for more focus on streamlining the common cases. In particular it'd be nice if there was a relatively homogeneous syntax as sketched below:

 for related operations starts to diverge a bit:

  var dict: [K:Set<V>] = // ...
  
  // read-only uses `if-let` (or `if shared`?)
  if let setForKey = dict[key] {
    // read `setForKey`
  }
  
  // simple in-place mutation
  if inout setForKey = dict[key] {
    setForKey.mutateSomehow()
  }
  
  // compound "in-place" mutation 
  if inout setForKey = dict[key] {
    setForKey.mutateSomehow()
    setForKey.mutateSomeOtherWay()
  }

  // simple "in-place" mutation w/follow-up
  if inout setForKey = dict[key] {
    setForKey.mutateSomehow()
    let shouldRemove = setForKey.isEmpty
    endScope(setForKey)
    if shouldRemove {
      dict.removeValue(forKey: key)
    }
  }

  // compound "in-place" mutation w/follow-up
  if inout setForKey = dict[key] {
    setForKey.mutateSomehow()
    setForKey.mutateSomeOtherWay()
    let shouldRemove = setForKey.isEmpty
    endScope(setForKey)
    if shouldRemove {
      dict.removeValue(forKey: key)
    }
  }
  
...which, again, all use approximately the same syntax for each of these scenarios. 

Contrast with what would be the ideal ways to write the above under the proposal AIUI:

  var dict: [K:Set<V>] = // ...
  
  // read-only uses `if-let` (or `if shared`?)
  if let setForKey = dict[key] {
    // read `setForKey`
  }
  
  // simple in-place mutation
  if let index = dict.index(of: key) {
    dict.values[index].mutateSomehow()
  }
  
  // compound "in-place" mutation, I
  // shorter, less-efficent due to repeated lookup
  if let index = dict.index(of: key) {
    dict.values[index].mutateSomehow()
    dict.values[index].mutateSomeOtherWay()
  }

  // compound "in-place" mutation, II
  // longer, more-efficent
  if let index = dict.index(of: key) {
    inout setForKey = &dict.values[index]
    setForKey.mutateSomehow()
    setForKey.mutateSomeOtherWay()
  }

  // simple "in-place" mutation w/follow-up, I
  // longer, less-efficient due to repeated lookup
  if let index = dict.index(of: key) {
    dict.values[index].mutateSomehow()
    if dict.values[index].isEmpty {
      dict.remove(at: index)
    }
  }

  // simple "in-place" mutation w/follow-up, II
  // longer, less-efficient due to repeated lookup
  if let index = dict.index(of: key) {
    inout setForKey = &dict.values[index]
    setForKey.mutateSomehow()
    let shouldRemove = setForKey.isEmpty
    endScope(shouldRemove)
    if shouldRemove {
      dict.remove(at: index)
    }
  }

  // compound "in-place" mutation w/follow-up, I
  // longer, less-efficient due to repeated lookups
  if let index = dict.index(of: key) {
    dict.values[index].mutateSomehow()
    dict.values[index].mutateSomeOtherWay()
    if dict.values[index].isEmpty {
      dict.remove(at: index)
    }
  }

  // compound "in-place" mutation w/follow-up, II
  // longer, less-efficient due to repeated lookup
  if let index = dict.index(of: key) {
    inout setForKey = &dict.values[index]
    setForKey.mutateSomehow()
    setForKey.mutateSomeOtherWay()
    let shouldRemove = setForKey.isEmpty
    endScope(shouldRemove)
    if shouldRemove {
      dict.remove(at: index)
    }
  }

...where my concern is less about what how particular scenario winds up looking and more that there’s such variance *between* the scenarios, at least when compared to a hypothetical `if inout` construct.

Again if `if inout` is actually part of the proposal I apologize for illustrating the consequence of its absence at such length.

> The technical obstacle to this is just that, so far, we've tried to make language features like for-loops use formal protocols.
> 
> An iteration protocol is going to have requirements like this:
>   generator iterate() -> Element
>   mutating generator iterateMutable() -> inout Element
> But there's no valid substitution that makes '(Key, inout Value)' equal either 'Element' or 'inout Element'.  So we'd have to do some special to make this work.
> 
> That said, no, there's no intrinsic technical reason this can't be made to work.

The explanation of wanting to stick to formal protocols makes perfect sense. I don’t think this should be a show-stopper, but I do think it’ll be a common request for a subsequent enhancement. 

Even in the interim it seems quite feasible to emulate the capability in any concrete case with enough willingness to crank out boilerplate; thus e.g. if someone truly needs `for (index, inout value) in collection.enumerated()` or `for (a, inout b) in zip(as,bs)` it isn’t as if they’re entirely stuck.

>> 3: Mutable Views
> 
> It's not sensible to have a type that conditionally conforms to a protocol based on context.

That did indeed seem like a big ask!

I’ll put in an early request to consider 

  @moveonly {
    struct File { } 
  }

…(or @scope(moveonly) or @context(moveonly) or @dialect(moveonly), etc.).

It’s confusing that `moveonly` essentially applies “inward”—it flags the code *within* a scope as using different assumptions, etc.—in contrast to most of the modifiers like `open`, `final`, `mutating`, etc., apply “outward” (by e.g. describing how visible the code in the scope is from other scopes, etc.).

>  However, the requirements of MutableCollection are all 'mutating', so you can't actually use them unless you have a mutable value.  So the way to fit what you're asking for into the ownership proposal is to make sure that clients of your view type only have a mutable value when the base was mutable, and the easiest way of getting that is to have the view be vended as storage rather than a return value.  If you think about it, a mutable view can't be an independent value anyway, because if code like this could work:
> 
>   var grid = ... // note that this is mutable
>   var view = grid.traversal(from: p, following: m) // should return a mutable view by design, right?
>   view.mutate() // reflected in grid, right?
> 
> then it completely destroys the value-type properties of 'grid', because 'view' should really be an independent value.

Without belaboring this, the point is indeed to “destroy the value-type properties of `grid`”, while trying to keep things “safe” by quarantining `view`—and the temporary relaxation of value semantics its existence implies—to a specific scope; thus under the status quo the use-site looks like this:

  var grid = // note that this is mutable
  // also note that `mutatingTraversal` is a `mutating` method...
  grid.mutatingTraversal(from: c, following: m) {
    (inout view: MutableGrid2DTraversal<T>) -> Void
    in
    // ^ this is the only public method that vends `MutableGrid2DTraversal<T>`, and
    // `MutableGrid2DTraversal<T>` has no public constructors, thus `view` is
    // “quarantined” to this scope unless we actively attempt to leak `view`...
    view.mutate()
  }

…which currently has two major drawbacks:

- (a) lots of code duplication: the immutable `Grid2DTraversal<T>` and the mutable `Mutable2DTraversal<T>`
- (b) the “quarantine” isn’t airtight, in that there are at least these three ways a `view` could escape:

  var grid = // …
  var leakedView: MutatingGrid2DTraversal<T>? = nil
  var otherLeakedView = grid.mutatingTraversal(from: c, following: m) {
    (inout view: MutableGrid2DTraversal<T>) -> MutableGrid2DTraversal<T>
    in
    view.mutate()
    // leak #1:
    leakedView = view
    // leak #2:
    self.liveDangerously(leaking: view)
    // leak #3:
    return view
  }

…which imho makes it fair to say “the above approach *encourages* ‘safe’ usage, but can’t *prevent* unsafe usage”.

Under the ownership proposal nothing changes for drawback (a): to stick with the design and behavior sketched above requires distinct types and thus a lot of duplicate or near-duplicate boilerplate.

Drawback (b) seems to fare better under the ownership proposal, provided that I make `MutatingGrid2DTraversal` a non-Copyable type; at least AIUI making `MutatingGrid2DTraversal` non-Copyable would effectively close leaks #1, #2, and #3, b/c:

- I would explicitly have to write e.g. `leakedView = move(view)` (a *very* unlikely accident)
- I would also have to supply a "replacement value” for `view` by the end of the scope (impossible due to lack of public constructors)

…at least if I understand correctly. Is this accurate? If accurate, how likely would it be that “failed to provide replacement value for `view` after move” would get caught at compile time?

Thanks again for providing such detailed clarifications about this proposal.

> The proposal suggests doing this instead with storage, so that the view is logically a (mutable) component of the grid.  So on the use side:
> 
>   var grid = ...
>   inout view = &grid[traversingFrom: p, following: m]
>   view.mutate()
> 
> and on the declaration side:
> 
>   struct Grid2D<T> {
>     subscript(traversingFrom corner: Grid2DCorner, following motion: Grid2DMotion) -> Grid2DTraversal<T> {
>       read {
>         yield Grid2DTraversal(...)
>       }
>       modify {
>         var traversal = Grid2DTraversal(...)
>         yield &traversal
>         // ensure changes were applied here
>       }
>     }
>   }
> 
> If you feel that the aesthetics of this leave something to be desired, that's a totally reasonable thing to discuss.
> 
> John.
> 
>> 
>> Anyways, it’s not clear to me, personally, whether or not the above is within the scope of any likely, concrete ownership system that’d follow from the manifesto or not…but if at all possible I’d prefer the eventual ownership system make it reasonable—and reasonably safe—to implement “small-c ‘collection’s that can safely-and-efficiently expose various *mutable* views as big-C `Collection`s”.
>> 
>> Apologies if all of the above considerations have answers that follow trivially from the manifesto; it’s just unclear personally whether the features described in the manifesto would work together to allow something like the above to be implemented more-reasonably than currently the case.
>> 
>>> On Feb 17, 2017, at 11:08 AM, John McCall via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>>> On Feb 17, 2017, at 4:50 AM, Adrian Zubarev <adrian.zubarev at devandartist.com <mailto:adrian.zubarev at devandartist.com>> wrote:
>>>> Hi John, would you mind creating a markdown document for this manifesto in https://github.com/apple/swift/tree/master/docs <https://github.com/apple/swift/tree/master/docs>? :)
>>>> 
>>>> 
>>> Yes, it should go in the repository.  That commit is pending, but the in meantime, you can see the document properly rendered at:
>>>   https://github.com/rjmccall/swift/blob/4c67c1d45b6f9649cc39bbb296d63663c1ef841f/docs/OwnershipManifesto.md <https://github.com/rjmccall/swift/blob/4c67c1d45b6f9649cc39bbb296d63663c1ef841f/docs/OwnershipManifesto.md>
>>> 
>>> John.
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170307/030ef68d/attachment.html>


More information about the swift-evolution mailing list