> Le 20 févr. 2017 à 23:16, John McCall <rjmcc...@apple.com> a écrit : > > >> On Feb 20, 2017, at 4:55 PM, Florent Bruneau <florent.brun...@intersec.com> >> wrote: >> >> >>> Le 20 févr. 2017 à 18:22, John McCall <rjmcc...@apple.com> a écrit : >>> >>>> >>>> On Feb 20, 2017, at 3:40 AM, Florent Bruneau >>>> <florent.brun...@intersec.com> wrote: >>>> >>>> Hi John, >>>> >>>> I've spent 3 hours reading the manifesto and its really very interesting. >>>> Despite the completeness of the included discussion, I have a few >>>> comments/concerns. >>>> >>>> >>>> The first one is about the use a Copyable protocol whose conformance must >>>> be explicit in the module of the definition of the type. Since copyability >>>> shouldn't be modified outside the module that define a type, using a >>>> protocol seems a bit weird since AFAIK no other protocol has this kind of >>>> constraints. I do understand this enables some idiomatic constructs (like >>>> you example where Array conforms to Copyable whenever its elements are >>>> copyable), but on the other hand this looks a bit confusing to me. I don't >>>> have a better solution, thought. >>> >>> On the one hand, I agree that it's not quite a normal protocol. On the >>> other hand... it's a capability of a type, which is the sort of thing we >>> normally express with a protocol. And because it's not just a single bit — >>> because a generic type can conditionally have that capability — if we made >>> it not a protocol, we'd just have to re-invent a new mechanism to declare >>> that, which would effectively be exactly like the protocol mechanism. >>> >>> I think it's much simpler to say that certain protocols are "policy" >>> protocols which are solely up to the author of the type to decide, and that >>> external, "retroactive" conformance is only allowed for non-policy >>> protocols. >> >> OK. You got me convinced this is probably the most consistent approach. Do >> you envision any other policy protocol? > > Not right away, but it's possible that eventually we might want equivalents > to some of the other magic protocols in Rust, like Send (which IIRC promises > that shared references are thread-safe; that's a similar sort of fundamental > property). > >>>> Secondly, in your discussion about variable independence, you talk about >>>> logical dependence: variables that are part of the same container. >>>> However, whenever this is some concurrency involved, there is also some >>>> kind of physical dependence. If you have two values stored physically in >>>> memory in the same cache line, and you may want to modify both >>>> independently and concurrently (one thread modifies the first value, the >>>> second modifies the other one), you have some false sharing which impacts >>>> the performance since the actual writes get sequentialized at CPU level. >>>> Is there any plan to take this kind of dependencies into account? >>> >>> Well, for one, CPUs may get cleverer here over time. So I would be >>> uncomfortable with cementing this too much in the language. >> >> I don't think this will dramatically improve over time. Moreover, >> spatial/physical dependencies may not be specific to cache lines. Another >> example would be two boolean packed into two bits. Those variables are not >> independent because writing into one of them requires loading and writing >> both of them (unless you exclusively use atomic operations). > > Correct. This design doesn't *completely* give up on allowing bit-packing in > classes, but it does make it much harder. That was considered. :) > >>> The bigger issue is that we don't have a mechanism for positively declaring >>> that two variables will be accessed from different threads. It would be >>> disastrous to over-interpret the *possibility* of concurrent access on >>> different class properties as a statement of *probability* of concurrent >>> access — because the only option there would be, what, to put each property >>> on its own cache line? And cache line sizes change, so this is not >>> statically knowable. >> >> This indeed wouldn't be statically checkable, but this could be (maybe as an >> opt-in?) dynamically checked. > > Yes, we could certainly do layout dynamically based on the actual cache-line > size. But I hope we can agree that putting every class property on its own > cache line by default is not actually acceptable. :)
Putting every value in its own cache line would clearly be a disaster. >>> I think it's fine for us to design the implementation around the idea that >>> it's not necessarily a good idea to access different properties >>> concurrently — that's true semantically, not just for performance, because >>> obviously that kind of concurrent access to a class takes a lot more care >>> and attention to get right — and leave it up to the programmer to respond. >>> >>> Allowing some sort of intelligent layout to prevent cache-line collisions >>> would certainly be a secondary goal of a concurrency design, though. >> >> OK >> >>> >>>> Thirdly, I'm a bit worried about the runtime impact of the dynamic >>>> enforcement of the law of exclusivity. It seems to me that in many use >>>> cases we will fall back to the dynamic enforcement because of the language >>>> is too dynamic to allow static enforcement. As discussed in the manifesto, >>>> it's not desirable (not doable) to put the full concept of ownership and >>>> lifetime in the type system. However, I cannot see how interaction between >>>> objects will work in the current approach. Let suppose I have a concurrent >>>> application that have a shared ressource that can be used by various >>>> workers. Workers can be either structures/classes or closures. >>>> >>>> ```swift >>>> struct Ressource { >>>> /* content is unimportant here */ >>>> } >>>> >>>> class Worker { >>>> shared ressource: Ressource >>>> >>>> init(ressource: shared Ressource) { >>>> self.ressource = ressource >>>> } >>>> >>>> /* ... */ >>>> } >>>> >>>> let ressource = Ressource() >>>> >>>> for i in 0..<10 { >>>> let worker = Worker(ressource: ressource) >>>> worker.schedule() >>>> } >>>> waitAllWorkers() >>>> ``` >>>> >>>> My understanding of the manifesto is that this kind of construct in not >>>> really part of it. Is there a way to enforce the workers' lifetime to be >>>> shorter than the lifetime of the ressource? >>> >>> If we had the ability to express "shared resource: Resource" as a class >>> property, yes, but you're correct that this is not currently allowed in the >>> design. That's mostly a limitation of *static* enforcement, though, >>> because we need to always be statically limiting the durations of >>> ephemerals, and having that transitively limit the lifetime of a class >>> instance is quite complicated. >> >> There may be dynamic approaches that could work. What we want is make sure >> that no dangling ephemeral remains after the end of life of the pointed >> ressource. We could imagine a solution based on weak-reference wherever we >> cannot statically enforce the lifetime rules. In that case, all references >> to the shared ressource, or all references to objects containing the >> ephemeral might be enforced to be weak and nil-ified when the value reaches >> its end of life. I'm aware there is a lot of issues with that idea. The >> first issue is that another thread may have a strong reference on the Worker >> because it is running a method of Worker when the Ressource becomes >> unavailable, which just breaks everything). Another issue is the runtime >> overhead of the bookkeeping requested to maintain the list of references >> associated to Ressource. >> >> Actually, it would probably be better to forget that idea :) > > Well, you could allocate a refcounted object to do the bookkeeping and then > implicitly capture that along with the shared value. That would be quite the > perf hit, though. > >>>> Finally, since I work a lot with imported C code, I'd like to know if >>>> there is some plan to make the ownership attributes works well with C >>>> code. I mean, you say that unsafe pointers just force a fall back to not >>>> enforcing the law of exclusivity, but in my day-to-day work, this is >>>> exactly where I need it the most: when I have to manipulate a structure >>>> that contain a pointer allocated by some C code, I cannot allow copy of >>>> that structure since that pointer may be reallocated and one of the copies >>>> would contain a dangling pointer. So, is there some way to opt-in for >>>> non-copyability of imported code? >>> >>> Well, copying the C structure isn't directly problematic in your case >>> because there's no automatic management of the pointer, but I see your >>> point that it can be an indirect problem because you might e.g. add a >>> method to the imported type which internally manages the pointer. It does >>> seem reasonable to have an attribute you can use in C that would tell Swift >>> to import something as a non-copyable type. >> >> OK >> >>> >>>> I tried, without success, to propose some way to improve pointer-passing >>>> between C and Swift [1] that could help tracking ownership when >>>> interacting with C code. >>>> >>>> [1] >>>> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170130/031199.html >>> >>> I think it's mostly that this is a really big topic and we're already >>> worried about how much we're doing in the next few months. Even just the >>> language-review aspect of a proposal like that would be an intimidatingly >>> broad commitment. >> >> Sorry about this. My whole point is simply: how can we make interaction with >> C safer in order to make it easier to migrate from C to Swift without >> rewriting everything from scratch. I had no idea you were working on the >> ownership principles at the time and it clearly looks like a big step toward >> that goal. > > Yeah, I definitely think this has to happen first. > > John. > >>> >>> When ownership is in place, we will at least have the right language tools >>> for theoretically, say, always importing pointers to a particular type as a >>> managed unique-reference type. So this is a major step in that direction. >> >> Thx. >> >> >>>> >>>>> Le 17 févr. 2017 à 09:25, John McCall via swift-evolution >>>>> <swift-evolution@swift.org> a écrit : >>>>> >>>>> Hello, swift-evolution. >>>>> >>>>> Memory ownership is a topic that keeps poking its head up here. Core team >>>>> members have mentioned several times that it's something we're interested >>>>> in >>>>> working on. Questions sometimes get referred back to it, saying stuff >>>>> like >>>>> "we're working on tools to let you control ARC a little better". It's >>>>> even on the >>>>> short list of high-priority features for Swift 4: >>>>> >>>>> https://github.com/apple/swift-evolution >>>>> >>>>> Memory ownership model: an (opt-in) Cyclone/Rust-inspired memory >>>>> ownership model is highly desired by systems programmers and for other >>>>> high-performance applications that want predictable and deterministic >>>>> performance. This feature will fundamentally shape the ABI, from low-level >>>>> language concerns such as "inout" and low-level "addressors" to its impact >>>>> on the standard library. While a full memory ownership model is likely too >>>>> large for Swift 4 stage 1, we need a comprehensive design to understand >>>>> how it will change the ABI. >>>>> >>>>> But that's all pretty vague. What is ownership? What does it mean for >>>>> programmers? Is somebody ever going to actually write that comprehensive >>>>> design that was supposed to come out during Stage 1? >>>>> >>>>> Well, here you go. >>>>> >>>>> I want to emphasize that this document is two things. It is a >>>>> *manifesto*, >>>>> because it describes the whole problem and presents a holistic solution >>>>> to it. >>>>> And it is a *meta-proposal*, because that holistic solution imagines and >>>>> a number >>>>> of changes, each of which is worthy of a proposal; it is, essentially, a >>>>> proposal >>>>> that we write a bunch of smaller proposals for each of these changes. >>>>> But it's >>>>> not actually a concrete proposal for those smaller changes: it's often >>>>> lacking in >>>>> that level of detail, and it leaves a number of questions open. This >>>>> document, itself, >>>>> will not undergo the formal evolution process. And it may be that some >>>>> of these >>>>> changes aren't really necessary, or that they need major reconsideration >>>>> before >>>>> they're appropriate for proposal. But our hope — my hope — is that this >>>>> document is sufficient for you to understand the holistic approach we're >>>>> suggesting, >>>>> or at least puts you in a position where you're comfortable asking >>>>> questions >>>>> and trying to figure it out. >>>>> >>>>> So, like I said, this isn't a formal proposal. What, then, are we hoping >>>>> to achieve? >>>>> Well, we want people to talk about it. We'd like to achieve a consensus >>>>> about >>>>> whether this basic approach makes sense for the language and achieves its >>>>> goals. >>>>> And, assuming that the consensus is "yes" and that we should go forward >>>>> with it, >>>>> we'd like this document — and this thread — to serve as an introduction >>>>> to the >>>>> topic that we can refer back to when we're proposing and discussing all >>>>> of those >>>>> changes in the weeks to come. >>>>> >>>>> With that said, let's get started. >>>>> >>>>> John. >>>>> >>>>> ------------------------------------------------------------------ >>>>> >>>>> # Ownership >>>>> >>>>> ## Introduction >>>>> >>>>> Adding "ownership" to Swift is a major feature with many benefits >>>>> for programmers. This document is both a "manifesto" and a >>>>> "meta-proposal" for ownership: it lays out the basic goals of >>>>> the work, describes a general approach for achieving those goals, >>>>> and proposes a number of specific changes and features, each of >>>>> which will need to be separately discussed in a smaller and more >>>>> targeted proposal. This document is intended to provide a framework >>>>> for understanding the contributions of each of those changes. >>>>> >>>>> ### Problem statement >>>>> >>>>> The widespread use of copy-on-write value types in Swift has generally >>>>> been a success. It does, however, come with some drawbacks: >>>>> >>>>> * Reference counting and uniqueness testing do impose some overhead. >>>>> >>>>> * Reference counting provides deterministic performance in most cases, >>>>> but that performance can still be complex to analyze and predict. >>>>> >>>>> * The ability to copy a value at any time and thus "escape" it forces >>>>> the underlying buffers to generally be heap-allocated. Stack allocation >>>>> is far more efficient but does require some ability to prevent, or at >>>>> least recognize, attempts to escape the value. >>>>> >>>>> Certain kinds of low-level programming require stricter performance >>>>> guarantees. Often these guarantees are less about absolute performance >>>>> than *predictable* performance. For example, keeping up with an audio >>>>> stream is not a taxing job for a modern processor, even with significant >>>>> per-sample overheads, but any sort of unexpected hiccup is immediately >>>>> noticeable by users. >>>>> >>>>> Another common programming task is to optimize existing code when >>>>> something >>>>> about it falls short of a performance target. Often this means finding >>>>> "hot spots" in execution time or memory use and trying to fix them in some >>>>> way. When those hot spots are due to implicit copies, Swift's current >>>>> tools for fixing the problem are relatively poor; for example, a >>>>> programmer >>>>> can fall back on using unsafe pointers, but this loses a lot of the >>>>> safety benefits and expressivity advantages of the library collection >>>>> types. >>>>> >>>>> We believe that these problems can be addressed with an opt-in set of >>>>> features that we collectively call *ownership*. >>>>> >>>>> ### What is ownership? >>>>> >>>>> *Ownership* is the responsibility of some piece of code to >>>>> eventually cause a value to be destroyed. An *ownership system* >>>>> is a set of rules or conventions for managing and transferring >>>>> ownership. >>>>> >>>>> Any language with a concept of destruction has a concept of >>>>> ownership. In some languages, like C and non-ARC Objective-C, >>>>> ownership is managed explicitly by programmers. In other >>>>> languages, like C++ (in part), ownership is managed by the >>>>> language. Even languages with implicit memory management still >>>>> have libraries with concepts of ownership, because there are >>>>> other program resources besides memory, and it is important >>>>> to understand what code has the responsibility to release >>>>> those resources. >>>>> >>>>> Swift already has an ownership system, but it's "under the covers": >>>>> it's an implementation detail that programmers have little >>>>> ability to influence. What we are proposing here is easy >>>>> to summarize: >>>>> >>>>> - We should add a core rule to the ownership system, called >>>>> the Law of Exclusivity, which requires the implementation >>>>> to prevent variables from being simultaneously accessed >>>>> in conflicting ways. (For example, being passed `inout` >>>>> to two different functions.) This will not be an opt-in >>>>> change, but we believe that most programs will not be >>>>> adversely affected. >>>>> >>>>> - We should add features to give programmers more control over >>>>> the ownership system, chiefly by allowing the propagation >>>>> of "shared" values. This will be an opt-in change; it >>>>> will largely consist of annotations and language features >>>>> which programmers can simply not use. >>>>> >>>>> - We should add features to allow programmers to express >>>>> types with unique ownership, which is to say, types that >>>>> cannot be implicitly copied. This will be an opt-in >>>>> feature intended for experienced programmers who desire >>>>> this level of control; we do not intend for ordinary >>>>> Swift programming to require working with such types. >>>>> >>>>> These three tentpoles together have the effect of raising >>>>> the ownership system from an implementation detail to a more >>>>> visible aspect of the language. They are also somewhat >>>>> inseparable, for reasons we'll explain, although of course they >>>>> can be prioritized differently. For these reasons, we will >>>>> talk about them as a cohensive feature called "ownership". >>>>> >>>>> ### A bit more detail >>>>> >>>>> The basic problem with Swift's current ownership system >>>>> is copies, and all three tentpoles of ownership are about >>>>> avoiding copies. >>>>> >>>>> A value may be used in many different places in a program. >>>>> The implementation has to ensure that some copy of the value >>>>> survives and is usable at each of these places. As long as >>>>> a type is copyable, it's always possible to satisfy that by >>>>> making more copies of the value. However, most uses don't >>>>> actually require ownership of their own copy. Some do: a >>>>> variable that didn't own its current value would only be >>>>> able to store values that were known to be owned by something >>>>> else, which isn't very useful in general. But a simple thing >>>>> like reading a value out of a class instance only requires that >>>>> the instance still be valid, not that the code doing the >>>>> read actually own a reference to it itself. Sometimes the >>>>> difference is obvious, but often it's impossible to know. >>>>> For example, the compiler generally doesn't know how an >>>>> arbitrary function will use its arguments; it just falls >>>>> back on a default rule for whether to pass ownership of >>>>> the value. When that default rule is wrong, the program >>>>> will end up making extra copies at runtime. So one simple >>>>> thing we can do is allow programs to be more explicit at >>>>> certain points about whether they need ownership or not. >>>>> >>>>> That closely dovetails with the desire to support non-copyable >>>>> types. Most resources require a unique point of destruction: >>>>> a memory allocation can only be freed once, a file can only >>>>> be closed once, a lock can only be released once, and so on. >>>>> An owning reference to such a resource is therefore naturally >>>>> unique and thus non-copyable. Of course, we can artificially >>>>> allow ownership to be shared by, say, adding a reference count >>>>> and only destroying the resource when the count reaches zero. >>>>> But this can substantially increase the overhead of working >>>>> with the resource; and worse, it introduces problems with >>>>> concurrency and re-entrancy. If ownership is unique, and the >>>>> language can enforce that certain operations on a resource >>>>> can only be performed by the code that owns the resource, >>>>> then by construction only one piece of code can perform those >>>>> operations at a time. As soon as ownership is shareable, >>>>> that property disappears. So it is interesting for the >>>>> language to directly support non-copyable types because they >>>>> allow the expression of optimally-efficient abstractions >>>>> over resources. However, working with such types requires >>>>> all of the abstraction points like function arguments to >>>>> be correctly annotated about whether they transfer ownership, >>>>> because the compiler can no longer just make things work >>>>> behind the scenes by adding copies. >>>>> >>>>> Solving either of these problems well will require us to >>>>> also solve the problem of non-exclusive access to variables. >>>>> Swift today allows nested accesses to the same variable; >>>>> for example, a single variable can be passed as two different >>>>> `inout` arguments, or a method can be passed a callback that >>>>> somehow accesses the same variable that the method was called on. >>>>> Doing this is mostly discouraged, but it's not forbidden, >>>>> and both the compiler and the standard library have to bend >>>>> over backwards to ensure that the program won't misbehave >>>>> too badly if it happens. For example, `Array` has to retain >>>>> its buffer during an in-place element modification; otherwise, >>>>> if that modification somehow reassigned the array variable, >>>>> the buffer would be freed while the element was still being >>>>> changed. Similarly, the compiler generally finds it difficult >>>>> to prove that values in memory are the same at different points >>>>> in a function, because it has to assume that any opaque >>>>> function call might rewrite all memory; as a result, it >>>>> often has to insert copies or preserve redundant loads >>>>> out of paranoia. Worse, non-exclusive access greatly >>>>> limits the usefulness of explicit annotations. For example, >>>>> a "shared" argument is only useful if it's really guaranteed >>>>> to stay valid for the entire call, but the only way to >>>>> reliably satisfy that for the current value of a variable >>>>> that can be re-entrantly modified is to make a copy and pass >>>>> that instead. It also makes certain important patterns >>>>> impossible, like stealing the current value of a variable >>>>> in order to build something new; this is unsound if the >>>>> variable can be accessed by other code in the middle. >>>>> The only solution to this is to establish a rule that prevents >>>>> multiple contexts from accessing the same variable at the >>>>> same time. This is what we propose to do with the Law >>>>> of Exclusivity. >>>>> >>>>> All three of these goals are closely linked and mutually >>>>> reinforcing. The Law of Exclusivity allows explicit annotations >>>>> to actually optimize code by default and enables mandatory >>>>> idioms for non-copyable types. Explicit annotations create >>>>> more optimization opportunities under the Law and enable >>>>> non-copyable types to function. Non-copyable types validate >>>>> that annotations are optimal even for copyable types and >>>>> create more situations where the Law can be satisfied statically. >>>>> >>>>> ### Criteria for success >>>>> >>>>> As discussed above, it is the core team's expectation that >>>>> ownership can be delivered as an opt-in enhancement to Swift. >>>>> Programmers should be able to largely ignore ownership and not >>>>> suffer for it. If this expectation proves to not be satisfiable, >>>>> we will reject ownership rather than imposing substantial >>>>> burdens on regular programs. >>>>> >>>>> The Law of Exclusivity will impose some new static and dynamic >>>>> restrictions. It is our belief that these restrictions will only >>>>> affect a small amount of code, and only code that does things >>>>> that we already document as producing unspecified results. >>>>> These restrictions, when enforced dynamically, will also hurt >>>>> performance. It is our hope that this will be "paid for" by >>>>> the improved optimization potential. We will also provide tools >>>>> for programmers to eliminate these safety checks where necessary. >>>>> We will discuss these restrictions in greater detail later in this >>>>> document. >>>>> >>>>> ## Core definitions >>>>> >>>>> ### Values >>>>> >>>>> Any discussion of ownership systems is bound to be at a lower >>>>> level of abstraction. We will be talking a lot about >>>>> implementation topics. In this context, when we say "value", >>>>> we mean a specific instance of a semantic, user-language value. >>>>> >>>>> For example, consider the following Swift code: >>>>> >>>>> ``` >>>>> var x = [1,2,3] >>>>> var y = x >>>>> ``` >>>>> >>>>> People would often say that `x` and `y` have the same value >>>>> at this point. Let's call this a *semantic value*. But at >>>>> the level of the implementation, because the variables `x` and `y` >>>>> can be independently modified, the value in `y` must be a >>>>> copy of the value in `x`. Let's call this a *value instance*. >>>>> A value instance can be moved around in memory and remain the >>>>> same value instance, but a copy always yields a new value instance. >>>>> For the remainder of this document, when we use "value" without any >>>>> qualification, we mean it in this low-level sense of value instance. >>>>> >>>>> What it means to copy or destroy a value instance depends on the type: >>>>> >>>>> * Some types do not require extra work besides copying their >>>>> byte-representation; we call these *trivial*. For example, >>>>> `Int` and `Float` are trivial types, as are ordinary `struct`s >>>>> and `enum`s containing only such values. Most of what we have >>>>> to say about ownership in this document doesn't apply to the >>>>> values of such types. However, the Law of Exclusivity will still >>>>> apply to them. >>>>> >>>>> * For reference types, the value instance is a reference to an object. >>>>> Copying the value instance means making a new reference, which >>>>> increases the reference count. Destroying the value instance means >>>>> destroying a reference, which decreases the reference count. Decreasing >>>>> the reference count can, of course, drop it to zero and thus destroy >>>>> the object, but it's important to remember that all this talk about >>>>> copying and destroying values means manipulating reference counts, >>>>> not copying the object or (necessarily) destroying it. >>>>> >>>>> * For copy-on-write types, the value instance includes a reference to >>>>> a buffer, which then works basically like a reference type. Again, >>>>> it is important to remember that copying the value doesn't mean >>>>> copying the contents of the buffer into a new buffer. >>>>> >>>>> There are similar rules for every kind of type. >>>>> >>>>> ### Memory >>>>> >>>>> In general, a value can be owned in one of two ways: it can be >>>>> "in flight", a temporary value owned by a specific execution context >>>>> which computed the value as an operand, or it can be "at rest", >>>>> stored in some sort of memory. >>>>> >>>>> We don't need to focus much on temporary values because their >>>>> ownership rules are straightforward. Temporary values are created >>>>> as the result of some expression; that expression is used in some >>>>> specific place; the value is needed in that place and not >>>>> thereafter; so the implementation should clearly take all possible >>>>> steps to forward the value directly to that place instead of >>>>> forcing it to be copied. Users already expect all of this to >>>>> happen, and there's nothing really to improve here. >>>>> >>>>> Therefore, most of our discussion of ownership will center around >>>>> values stored in memory. There are five closely related concepts >>>>> in Swift's treatment of memory. >>>>> >>>>> A *storage declaration* is the language-syntax concept of a declaration >>>>> that can be treated in the language like memory. Currently, these are >>>>> always introduced with `let`, `var`, and `subscript`. A storage >>>>> declaration has a type. It also has an implementation which defines >>>>> what it means to read or write the storage. The default implementation >>>>> of a `var` or `let` just creates a new variable to store the value, >>>>> but storage declarations can also be computed, and so there needn't >>>>> be any variables at all behind one. >>>>> >>>>> A *storage reference expression* is the syntax concept of an expression >>>>> that refers to storage. This is similar to the concept from other >>>>> languages of an "l-value", except that it isn't necessarily usable on >>>>> the left side of an assignment because the storage doesn't have to be >>>>> mutable. >>>>> >>>>> A *storage reference* is the language-semantics concept of a fully >>>>> filled-in reference to a specific storage declaration. In other >>>>> words, it is the result of evaluating a storage reference expression >>>>> in the abstract, without actually accessing the storage. If the >>>>> storage is a member, this includes a value or storage reference >>>>> for the base. If the storage is a subscript, this includes a value >>>>> for the index. For example, a storage reference expression like >>>>> `widgets[i].weight` might abstractly evaluate to this storage reference: >>>>> >>>>> * the storage for the property `var weight: Double` of >>>>> * the storage for the subscript `subscript(index: Int)` at index value >>>>> `19: Int` of >>>>> * the storage for the local variable `var widgets: [Widget]` >>>>> >>>>> A *variable* is the semantics concept of a unique place in >>>>> memory that stores a value. It's not necessarily mutable, at least >>>>> as we're using it in this document. Variables are usually created for >>>>> storage declarations, but they can also be created dynamically in >>>>> raw memory, e.g. using UnsafeRawPointer. A variable always has a >>>>> specific type. It also has a *lifetime*, i.e. a point in the language >>>>> semantics where it comes into existence and a point (or several) >>>>> where it is destroyed. >>>>> >>>>> A *memory location* is a contiguous range of addressable memory. In >>>>> Swift, this is mostly an implementation concept. Swift does not >>>>> guarantee that any particular variable will have a consistent memory >>>>> location throughout its lifetime, or in fact be stored in a memory >>>>> location at all. But a variable can sometimes be temporarily forced >>>>> to exist at a specific, consistent location: e.g. it can be passed >>>>> `inout` to `withUnsafeMutablePointer`. >>>>> >>>>> ### Accesses >>>>> >>>>> A particular evaluation of a storage reference expression is >>>>> called an access. Accesses come in three kinds: *reads*, >>>>> *assignments*, and *modifications*. Assignments and modifications >>>>> are both *writes*, with the difference being that an assignment >>>>> completely replaces the old value without reading it, while a >>>>> modification does rely on the old value. >>>>> >>>>> All storage reference expressions are classified into one of these >>>>> three kinds of access based on the context in which the expression >>>>> appears. It is important to note that this decision is superficial: >>>>> it relies only on the semantic rules of the immediate context, not >>>>> on a deeper analysis of the program or its dynamic behavior. >>>>> For example, a storage reference passed as an `inout` argument >>>>> is always evaluated as a modification in the caller, regardless >>>>> of whether the callee actually uses the current value, performs >>>>> any writes to it, or even refers to it at all. >>>>> >>>>> The evaluation of a storage reference expression is divided into >>>>> two phases: it is first formally evaluated to a storage reference, >>>>> and then a formal access to that storage reference occurs for some >>>>> duration. The two phases are often evaluated in immediate >>>>> succession, but they can be separated in complex cases, such as >>>>> when an `inout` argument is not the last argument to a call. >>>>> The purpose of this phase division is to minimize the duration of >>>>> the formal access while still preserving, to the greatest extent >>>>> possible, Swift's left-to-right evaluation rules. >>>>> >>>>> ## The Law of Exclusivity >>>>> >>>>> With all of that established, we can succinctly state the first >>>>> part of this proposal, the Law of Exclusivity: >>>>> >>>>>> If a storage reference expression evaluates to a storage >>>>>> reference that is implemented by a variable, then the formal >>>>>> access duration of that access may not overlap the formal >>>>>> access duration of any other access to the same variable >>>>>> unless both accesses are reads. >>>>> >>>>> This is intentionally vague: it merely says that accesses >>>>> "may not" overlap, without specifying how that will be >>>>> enforced. This is because we will use different enforcement >>>>> mechanisms for different kinds of storage. We will discuss >>>>> those mechanisms in the next major section. First, however, >>>>> we need to talk in general about some of the implications of >>>>> this rule and our approach to satisfying it. >>>>> >>>>> ### Duration of exclusivity >>>>> >>>>> The Law says that accesses must be exclusive for their entire >>>>> formal access duration. This duration is determined by the >>>>> immediate context which causes the access; that is, it's a >>>>> *static* property of the program, whereas the safety problems >>>>> we laid out in the introduction are *dynamic*. It is a general >>>>> truth that static approaches to dynamic problems can only be >>>>> conservatively correct: there will be dynamically-reasonable >>>>> programs that are nonetheless rejected. It is fair to ask how >>>>> that general principle applies here. >>>>> >>>>> For example, when storage is passed as an `inout` argument, the >>>>> access lasts for the duration of the call. This demands >>>>> caller-side enforcement that no other accesses can occur to >>>>> that storage during the call. Is it possible that this is too >>>>> coarse-grained? After all, there may be many points within the >>>>> called function where it isn't obviously using its `inout` >>>>> argument. Perhaps we should track accesses to `inout` arguments >>>>> at a finer-grained level, within the callee, instead of attempting >>>>> to enforce the Law of Exclusivity in the caller. The problem >>>>> is that that idea is simply too dynamic to be efficiently >>>>> implemented. >>>>> >>>>> A caller-side rule for `inout` has one key advantage: the >>>>> caller has an enormous amount of information about what >>>>> storage is being passed. This means that a caller-side rule >>>>> can often be enforced purely statically, without adding dynamic >>>>> checks or making paranoid assumptions. For example, suppose >>>>> that a function calls a `mutating` method on a local variable. >>>>> (Recall that `mutating` methods are passed `self` as an `inout` >>>>> argument.) Unless the variable has been captured in an >>>>> escaping closure, the function can easily examine every >>>>> access to the variable to see that none of them overlap >>>>> the call, thus proving that the rule is satisfied. Moreover, >>>>> that guarantee is then passed down to the callee, which can >>>>> use that information to prove the safety of its own accesses. >>>>> >>>>> In contrast, a callee-side rule for `inout` cannot take >>>>> advantage of that kind of information: the information is >>>>> simply discarded at the point of the call. This leads to the >>>>> widespread optimization problems that we see today, as >>>>> discussed in the introduction. For example, suppose that >>>>> the callee loads a value from its argument, then calls >>>>> a function which the optimizer cannot reason about: >>>>> >>>>> ``` >>>>> extension Array { >>>>> mutating func organize(_ predicate: (Element) -> Bool) { >>>>> let first = self[0] >>>>> if !predicate(first) { return } >>>>> ... >>>>> // something here uses first >>>>> } >>>>> } >>>>> ``` >>>>> >>>>> Under a callee-side rule, the optimizer must copy `self[0]` >>>>> into `first` because it must assume (paranoidly) that >>>>> `predicate` might somehow turn around and modify the >>>>> variable that `self` was bound to. Under a caller-side >>>>> rule, the optimizer can use the copy of value held in the >>>>> array element for as long as it can continue to prove that >>>>> the array hasn't been modified. >>>>> >>>>> Moreover, as the example above suggests, what sort of code >>>>> would we actually be enabling by embracing a callee-side rule? >>>>> A higher-order operation like this should not have to worry >>>>> about the caller passing in a predicate that re-entrantly >>>>> modifies the array. Simple implementation choices, like >>>>> making the local variable `first` instead of re-accessing >>>>> `self[0]` in the example above, would become semantically >>>>> important; maintaining any sort of invariant would be almost >>>>> inconceivable. It is no surprise that Swift's libraries >>>>> generally forbid this kind of re-entrant access. But, >>>>> since the library can't completely prevent programmers >>>>> from doing it, the implementation must nonetheless do extra >>>>> work at runtime to prevent such code from running into >>>>> undefined behavior and corrupting the process. Because it >>>>> exists solely to work around the possibility of code that >>>>> should never occur in a well-written program, we see this >>>>> as no real loss. >>>>> >>>>> Therefore, this proposal generally proposes access-duration >>>>> rules like caller-side `inout` enforcement, which allow >>>>> substantial optimization opportunities at little semantic >>>>> cost. >>>>> >>>>> ### Components of value and reference types >>>>> >>>>> We've been talking about *variables* a lot. A reader might >>>>> reasonably wonder what all this means for *properties*. >>>>> >>>>> Under the definition we laid out above, a property is a >>>>> storage declaration, and a stored property creates a >>>>> corresponding variable in its container. Accesses to that >>>>> variable obviously need to obey the Law of Exclusivity, but are >>>>> there any additional restrictions in play due to the fact >>>>> that the properties are organized together into a container? >>>>> In particular, should the Law of Exclusivity prevent accesses >>>>> to different properties of the same variable or value from >>>>> overlapping? >>>>> >>>>> Properties can be classified into three groups: >>>>> - instance properties of value types, >>>>> - instance properties of reference types, and >>>>> - `static` and `class` properties on any kind of type. >>>>> >>>>> We propose to always treat reference-type and `static` properties >>>>> as independent from one another other, but to treat value-type >>>>> properties as generally non-independent outside of a specific >>>>> (but important) special case. That's a potentially significant >>>>> restriction, and it's reasonable to wonder both why it's necessary >>>>> and why we need to draw this distinction between different >>>>> kinds of property. There are three reasons. >>>>> >>>>> #### Independence and containers >>>>> >>>>> The first relates to the container. >>>>> >>>>> For value types, it is possible to access both an individual >>>>> property and the entire aggregate value. It is clear that an >>>>> access to a property can conflict with an access to the aggregate, >>>>> because an access to the aggregate is essentially an access to >>>>> all of the properties at once. For example, consider a variable >>>>> (not necessarily a local one) `p: Point` with stored properties >>>>> `x`, `y`, and `z`. If it were possible to simultaneously and >>>>> independently modify `p` and `p.x`, that would be an enormous >>>>> hole in the Law of Exclusivity. So we do need to enforce the >>>>> Law somehow here. We have three options. >>>>> >>>>> (This may make more sense after reading the main section >>>>> about enforcing the Law.) >>>>> >>>>> The first option is to simply treat `p.x` as also an access to `p`. >>>>> This neatly eliminates the hole because whatever enforcement >>>>> we're using for `p` will naturally prevent conflicting accesses >>>>> to it. But this will also prevent accesses to different >>>>> properties from overlapping, because each will cause an access >>>>> to `p`, triggering the enforcement. >>>>> >>>>> The other two options involve reversing that relationship. >>>>> We could split enforcement out for all the individual stored >>>>> properties, not for the aggregate: an access to `p` would be >>>>> treated as an access to `p.x`, `p.y`, and `p.z`. Or we could >>>>> parameterize enforcement and teach it to record the specific >>>>> path of properties being accessed: "", ".x", and so on. >>>>> Unfortunately, there are two problems with these schemes. >>>>> The first is that we don't always know the full set of >>>>> properties, or which properties are stored; the implementation >>>>> of a type might be opaque to us due to e.g. generics or >>>>> resilience. An access to a computed property must be treated >>>>> as an access to the whole value because it involves passing >>>>> the variable to a getter or setter either `inout` or `shared`; >>>>> thus it does actually conflict with all other properties. >>>>> Attempting to make things work despite that by using dynamic >>>>> information would introduce ubiquitous bookkeeping into >>>>> value-type accessors, endangering the core design goal of >>>>> value types that they serve as a low-cost abstraction tool. >>>>> The second is that, while these schemes can be applied to >>>>> static enforcement relatively easily, applying them to >>>>> dynamic enforcement would require a fiendish amount of >>>>> bookkeeping to be carried out dynamically; this is simply >>>>> not compatible with our performance goals. >>>>> >>>>> Thus, while there is a scheme which allows independent access >>>>> to different properties of the same aggregate value, it >>>>> requires us to be using static enforcement for the aggregate >>>>> access and to know that both properties are stored. This is an >>>>> important special case, but it is just a special case. >>>>> In all other cases, we must fall back on the general rule >>>>> that an access to a property is also an access to the aggregate. >>>>> >>>>> These considerations do not apply to `static` properties and >>>>> properties of reference types. There are no language constructs >>>>> in Swift which access every property of a class simultaneously, >>>>> and it doesn't even make sense to talk about "every" `static` >>>>> property of a type because an arbitrary module can add a new >>>>> one at any time. >>>>> >>>>> #### Idioms of independent access >>>>> >>>>> The second relates to user expectations. >>>>> >>>>> Preventing overlapping accesses to different properties of a >>>>> value type is at most a minor inconvenience. The Law of Exclusivity >>>>> prevents "spooky action at a distance" with value types anyway: >>>>> for example, calling a method on a variable cannot kick off a >>>>> non-obvious sequence of events which eventually reach back and >>>>> modify the original variable, because that would involve two >>>>> conflicting and overlapping accesses to the same variable. >>>>> >>>>> In contrast, many established patterns with reference types >>>>> depend on exactly that kind of notification-based update. In >>>>> fact, it's not uncommon in UI code for different properties of >>>>> the same object to be modified concurrently: one by the UI and >>>>> the other by some background operation. Preventing independent >>>>> access would break those idioms, which is not acceptable. >>>>> >>>>> As for `static` properties, programmers expect them to be >>>>> independent global variables; it would make no sense for >>>>> an access to one global to prevent access to another. >>>>> >>>>> #### Independence and the optimizer >>>>> >>>>> The third relates to the optimization potential of properties. >>>>> >>>>> Part of the purpose of the Law of Exclusivity is that it >>>>> allows a large class of optimizations on values. For example, >>>>> a non-`mutating` method on a value type can assume that `self` >>>>> remains exactly the same for the duration of the method. It >>>>> does not have to worry that an unknown function it calls >>>>> in the middle will somehow reach back and modify `self`, >>>>> because that modification would violate the Law. Even in a >>>>> `mutating` method, no code can access `self` unless the >>>>> method knows about it. Those assumptions are extremely >>>>> important for optimizing Swift code. >>>>> >>>>> However, these assumptions simply cannot be done in general >>>>> for the contents of global variables or reference-type >>>>> properties. Class references can be shared arbitrarily, >>>>> and the optimizer must assume that an unknown function >>>>> might have access to the same instance. And any code in >>>>> the system can potentially access a global variable (ignoring >>>>> access control). So the language implementation would >>>>> gain little to nothing from treating accesses to different >>>>> properties as non-independent. >>>>> >>>>> #### Subscripts >>>>> >>>>> Much of this discussion also applies to subscripts, though >>>>> in the language today subscripts are never technically stored. >>>>> Accessing a component of a value type through a subscript >>>>> is treated as accessing the entire value, and so is considered >>>>> to overlap any other access to the value. The most important >>>>> consequence of this is that two different array elements cannot >>>>> be simultaneously accessed. This will interfere with certain >>>>> common idioms for working with arrays, although some cases >>>>> (like concurrently modifying different slices of an array) >>>>> are already quite problematic in Swift. We believe that we >>>>> can mitigate the majority of the impact here with targetted >>>>> improvements to the collection APIs. >>>>> >>>>> ## Enforcing the Law of Exclusivity >>>>> >>>>> There are three available mechanisms for enforcing the Law of >>>>> Exclusivity: static, dynamic, and undefined. The choice >>>>> of mechanism must be decidable by a simple inspection of the >>>>> storage declaration, because the definition and all of its >>>>> direct accessors must agree on how it is done. Generally, >>>>> it will be decided by the kind of storage being declared, >>>>> its container (if any), and any attributes that might be >>>>> present. >>>>> >>>>> ### Static enforcement >>>>> >>>>> Under static enforcement, the compiler detects that the >>>>> law is being violated and reports an error. This is the >>>>> preferred mechanism where possible because it is safe, reliable, >>>>> and imposes no runtime costs. >>>>> >>>>> This mechanism can only be used when it is perfectly decidable. >>>>> For example, it can be used for value-type properties because >>>>> the Law, recursively applied, ensures that the base storage is >>>>> being exclusively accessed. It cannot be used for ordinary >>>>> reference-type properties because there is no way to prove in >>>>> general that a particular object reference is the unique >>>>> reference to an object. However, if we supported >>>>> uniquely-referenced class types, it could be used for their >>>>> properties. >>>>> >>>>> In some cases, where desired, the compiler may be able to >>>>> preserve source compatibility and avoid an error by implicitly >>>>> inserting a copy instead. This likely something we would only >>>>> do in a source-compatibility mode. >>>>> >>>>> Static enforcement will be used for: >>>>> >>>>> - immutable variables of all kinds, >>>>> >>>>> - local variables, except as affected by the use of closures >>>>> (see below), >>>>> >>>>> - `inout` arguments, and >>>>> >>>>> - instance properties of value types. >>>>> >>>>> ### Dynamic enforcement >>>>> >>>>> Under dynamic enforcement, the implementation will maintain >>>>> a record of whether each variable is currently being accessed. >>>>> If a conflict is detected, it will trigger a dynamic failure. >>>>> The compiler may emit an error statically if it detects that >>>>> dynamic enforcement will always detect a conflict. >>>>> >>>>> The bookkeeping requires two bits per variable, using a tri-state >>>>> of "unaccessed", "read", and "modified". Although multiple >>>>> simultaneous reads can be active at once, a full count can be >>>>> avoided by saving the old state across the access, with a little >>>>> cleverness. >>>>> >>>>> The bookkeeping is intended to be best-effort. It should reliably >>>>> detect deterministic violations. It is not required to detect >>>>> race conditions; it often will, and that's good, but it's not >>>>> required to. It *is* required to successfully handle the case >>>>> of concurrent reads without e.g. leaving the bookkeeping record >>>>> permanently in the "read" state. But it is acceptable for the >>>>> concurrent-reads case to e.g. leave the bookkeeping record in >>>>> the "unaccessed" case even if there are still active readers; >>>>> this permits the bookkeeping to use non-atomic operations. >>>>> However, atomic operations would have to be used if the bookkeeping >>>>> records were packed into a single byte for, say, different >>>>> properties of a class, because concurrent accesses are >>>>> allowed on different variables within a class. >>>>> >>>>> When the compiler detects that an access is "instantaneous", >>>>> in the sense that none of the code executed during the access >>>>> can possibly cause a re-entrant access to the same variable, >>>>> it can avoid updating the bookkeeping record and instead just >>>>> check that it has an appropriate value. This is common for >>>>> reads, which will often simply copy the value during the access. >>>>> When the compiler detects that all possible accesses are >>>>> instantaneous, e.g. if the variable is `private` or `internal`, >>>>> it can eliminate all bookkeeping. We expect this to be fairly >>>>> common. >>>>> >>>>> Dynamic enforcement will be used for: >>>>> >>>>> - local variables, when necessary due to the use of closures >>>>> (see below), >>>>> >>>>> - instance properties of class types, >>>>> >>>>> - `static` and `class` properties, and >>>>> >>>>> - global variables. >>>>> >>>>> We should provide an attribute to allow dynamic enforcement >>>>> to be downgraded to undefined enforcement for a specific >>>>> property or class. It is likely that some clients will find >>>>> the performance consequences of dynamic enforcement to be >>>>> excessive, and it will be important to provide them an opt-out. >>>>> This will be especially true in the early days of the feature, >>>>> while we're still exploring implementation alternatives and >>>>> haven't yet implemented any holistic optimizations. >>>>> >>>>> Future work on isolating class instances may allow us to >>>>> use static enforcement for some class instance properties. >>>>> >>>>> ### Undefined enforcement >>>>> >>>>> Undefined enforcement means that conflicts are not detected >>>>> either statically or dynamically, and instead simply have >>>>> undefined behavior. This is not a desireable mechanism >>>>> for ordinary code given Swift's "safe by default" design, >>>>> but it's the only real choice for things like unsafe pointers. >>>>> >>>>> Undefined enforcement will be used for: >>>>> >>>>> - the `memory` properties of unsafe pointers. >>>>> >>>>> ### Enforcement for local variables captured by closures >>>>> >>>>> Our ability to statically enforce the Law of Exclusivity >>>>> relies on our ability to statically reason about where >>>>> uses occur. This analysis is usually straightforward for a >>>>> local variable, but it becomes complex when the variable is >>>>> captured in a closure because the control flow leading to >>>>> the use can be obscured. A closure can potentially be >>>>> executed re-entrantly or concurrently, even if it's known >>>>> not to escape. The following principles apply: >>>>> >>>>> - If a closure `C` potentially escapes, then for any variable >>>>> `V` captured by `C`, all accesses to `V` potentially executed >>>>> after a potential escape (including the accesses within `C` >>>>> itself) must use dynamic enforcement unless all such acesses >>>>> are reads. >>>>> >>>>> - If a closure `C` does not escape a function, then its >>>>> use sites within the function are known; at each, the closure >>>>> is either directly called or used as an argument to another >>>>> call. Consider the set of non-escaping closures used at >>>>> each such call. For each variable `V` captured by `C`, if >>>>> any of those closures contains a write to `V`, all accesses >>>>> within those closures must use dynamic enforcement, and >>>>> the call is treated for purposes of static enforcement >>>>> as if it were a write to `V`; otherwise, the accesses may use >>>>> static enforcement, and the call is treated as if it were a >>>>> read of `V`. >>>>> >>>>> It is likely that these rules can be improved upon over time. >>>>> For example, we should be able to improve on the rule for >>>>> direct calls to closures. >>>>> >>>>> ## Explicit tools for ownership >>>>> >>>>> ### Shared values >>>>> >>>>> A lot of the discussion in this section involves the new concept >>>>> of a *shared value*. As the name suggests, a shared value is a >>>>> value that has been shared with the current context by another >>>>> part of the program that owns it. To be consistent with the >>>>> Law of Exclusivity, because multiple parts of the program can >>>>> use the value at once, it must be read-only in all of them >>>>> (even the owning context). This concept allows programs to >>>>> abstract over values without copying them, just like `inout` >>>>> allows programs to abstract over variables. >>>>> >>>>> (Readers familiar with Rust will see many similarities between >>>>> shared values and Rust's concept of an immutable borrow.) >>>>> >>>>> When the source of a shared value is a storage reference, the >>>>> shared value acts essentially like an immutable reference >>>>> to that storage. The storage is accessed as a read for the >>>>> duration of the shared value, so the Law of Exclusivity >>>>> guarantees that no other accesses will be able to modify the >>>>> original variable during the access. Some kinds of shared >>>>> value may also bind to temporary values (i.e. an r-value). >>>>> Since temporary values are always owned by the current >>>>> execution context and used in one place, this poses no >>>>> additional semantic concerns. >>>>> >>>>> A shared value can be used in the scope that binds it >>>>> just like an ordinary parameter or `let` binding. >>>>> If the shared value is used in a place that requires >>>>> ownership, Swift will simply implicitly copy the value — >>>>> again, just like an ordinary parameter or `let` binding. >>>>> >>>>> #### Limitations of shared values >>>>> >>>>> This section of the document describes several ways to form >>>>> and use shared values. However, our current design does not >>>>> provide general, "first-class" mechanisms for working with them. >>>>> A program cannot return a shared value, construct an array >>>>> of shared values, store shared values into `struct` fields, >>>>> and so on. These limitations are similar to the existing >>>>> limitations on `inout` references. In fact, the similarities >>>>> are so common that it will be useful to have a term that >>>>> encompasses both: we will call them *ephemerals*. >>>>> >>>>> The fact that our design does not attempt to provide first-class >>>>> facilities for ephemerals is a well-considered decision, >>>>> born from a trio of concerns: >>>>> >>>>> - We have to scope this proposal to something that can >>>>> conceivably be implemented in the coming months. We expect this >>>>> proposal to yield major benefits to the language and its >>>>> implementaton, but it is already quite broad and aggressive. >>>>> First-class ephemerals would add enough complexity to the >>>>> implementation and design that they are clearly out of scope. >>>>> Furthermore, the remaining language-design questions are >>>>> quite large; several existing languages have experimented >>>>> with first-class ephemerals, and the results haven't been >>>>> totally satisfactory. >>>>> >>>>> - Type systems trade complexity for expressivity. >>>>> You can always accept more programs by making the type >>>>> system more sophisticated, but that's not always a good >>>>> trade-off. The lifetime-qualification systems behind >>>>> first-class references in languages like Rust add a lot >>>>> of complexity to the user model. That complexity has real >>>>> costs for users. And it's still inevitably necessary >>>>> to sometimes drop down to unsafe code to work around the >>>>> limitations of the ownership system. Given that a line >>>>> does have to drawn somewhere, it's not completely settled >>>>> that lifetime-qualification systems deserve to be on the >>>>> Swift side of the line. >>>>> >>>>> - A Rust-like lifetime system would not necessarily be >>>>> as powerful in Swift as it is in Rust. Swift intentionally >>>>> provides a language model which reserves a lot of >>>>> implementation flexibility to both the authors of types >>>>> and to the Swift compiler itself. >>>>> >>>>> For example, polymorphic storage is quite a bit more >>>>> flexible in Swift than it is in Rust. A >>>>> `MutableCollection` in Swift is required to implement a >>>>> `subscript` that provides accessor to an element for an index, >>>>> but the implementation can satisfy this pretty much any way >>>>> it wants. If generic code accesses this `subscript`, and it >>>>> happens to be implemented in a way that provides direct access >>>>> to the underlying memory, then the access will happen in-place; >>>>> but if the `subscript` is implemented with a computed getter >>>>> and setter, then the access will happen in a temporary >>>>> variable and the getter and setter will be called as necessary. >>>>> This only works because Swift's access model is highly >>>>> lexical and maintains the ability to run arbitrary code >>>>> at the end of an access. Imagine what it would take to >>>>> implement a loop that added these temporary mutable >>>>> references to an array — each iteration of the loop would >>>>> have to be able to queue up arbitrary code to run as a clean-up >>>>> when the function was finished with the array. This would >>>>> hardly be a low-cost abstraction! A more Rust-like >>>>> `MutableCollection` interface that worked within the >>>>> lifetime rules would have to promise that the `subscript` >>>>> returned a pointer to existing memory; and that wouldn't >>>>> allow a computed implementation at all. >>>>> >>>>> A similar problem arises even with simple `struct` members. >>>>> The Rust lifetime rules say that, if you have a pointer to >>>>> a `struct`, you can make a pointer to a field of that `struct` >>>>> and it'll have the same lifetime as the original pointer. >>>>> But this assumes not only that the field is actually stored >>>>> in memory, but that it is stored *simply*, such that you can >>>>> form a simple pointer to it and that pointer will obey the >>>>> standard ABI for pointers to that type. This means that >>>>> Rust cannot use layout optimizations like packing boolean >>>>> fields together in a byte or even just decreasing the >>>>> alignment of a field. This is not a guarantee that we are >>>>> willing to make in Swift. >>>>> >>>>> For all of these reasons, while we remain theoretically >>>>> interested in exploring the possibilities of a more >>>>> sophisticated system that would allow broader uses of >>>>> ephemerals, we are not proposing to take that on now. Since >>>>> such a system would primarily consist of changes to the type >>>>> system, we are not concerned that this will cause ABI-stability >>>>> problems in the long term. Nor are we concerned that we will >>>>> suffer from source incompatibilities; we believe that any >>>>> enhancements here can be done as extensions and generalizations >>>>> of the proposed features. >>>>> >>>>> ### Local ephemeral bindings >>>>> >>>>> It is already a somewhat silly limitation that Swift provides >>>>> no way to abstract over storage besides passing it as an >>>>> `inout` argument. It's an easy limitation to work around, >>>>> since programmers who want a local `inout` binding can simply >>>>> introduce a closure and immediately call it, but that's an >>>>> awkward way of achieving something that ought to be fairly easy. >>>>> >>>>> Shared values make this limitation even more apparent, because >>>>> a local shared value is an interesting alternative to a local `let`: >>>>> it avoids a copy at the cost of preventing other accesses to >>>>> the original storage. We would not encourage programmers to >>>>> use `shared` instead of `let` throughout their code, especially >>>>> because the optimizer will often be able to eliminate the copy >>>>> anyway. However, the optimizer cannot always remove the copy, >>>>> and so the `shared` micro-optimization can be useful in select >>>>> cases. Furthermore, eliminating the formal copy may also be >>>>> semantically necessary when working with non-copyable types. >>>>> >>>>> We propose to remove this limitation in a straightforward way: >>>>> >>>>> ``` >>>>> inout root = &tree.root >>>>> >>>>> shared elements = self.queue >>>>> ``` >>>>> >>>>> The initializer is required and must be a storage reference >>>>> expression. The access lasts for the remainder of the scope. >>>>> >>>>> ### Function parameters >>>>> >>>>> Function parameters are the most important way in which >>>>> programs abstract over values. Swift currently provides >>>>> three kinds of argument-passing: >>>>> >>>>> - Pass-by-value, owned. This is the rule for ordinary >>>>> arguments. There is no way to spell this explicitly. >>>>> >>>>> - Pass-by-value, shared. This is the rule for the `self` >>>>> arguments of `nonmutating` methods. There is no way to >>>>> spell this explicitly. >>>>> >>>>> - Pass-by-reference. This is the rule used for `inout` >>>>> arguments and the `self` arguments of `mutating` methods. >>>>> >>>>> Our proposal here is just to allow the non-standard >>>>> cases to be spelled explicitly: >>>>> >>>>> - A function argument can be explicitly declared `owned`: >>>>> >>>>> ``` >>>>> func append(_ values: owned [Element]) { >>>>> ... >>>>> } >>>>> ``` >>>>> >>>>> This cannot be combined with `shared` or `inout`. >>>>> >>>>> This is just an explicit way of writing the default, and >>>>> we do not expect that users will write it often unless >>>>> they're working with non-copyable types. >>>>> >>>>> - A function argument can be explicitly declared `shared`. >>>>> >>>>> ``` >>>>> func ==(left: shared String, right: shared String) -> Bool { >>>>> ... >>>>> } >>>>> ``` >>>>> >>>>> This cannot be combined with `owned` or `inout`. >>>>> >>>>> If the function argument is a storage reference expression, >>>>> that storage is accessed as a read for the duration of the >>>>> call. Otherwise, the argument expression is evaluated as >>>>> an r-value and that temporary value is shared for the call. >>>>> It's important to allow temporary values to be shared for >>>>> function arguments because many function parameters will be >>>>> marked as `shared` simply because the functions don't >>>>> actually from owning that parameter, not because it's in >>>>> any way semantically important that they be passed a >>>>> reference to an existing variable. For example, we expect >>>>> to change things like comparison operators to take their >>>>> parameters `shared`, because it needs to be possible to >>>>> compare non-copyable values without claiming them, but >>>>> this should not prevent programmers from comparing things >>>>> to literal values. >>>>> >>>>> Like `inout`, this is part of the function type. Unlike >>>>> `inout`, most function compatibility checks (such as override >>>>> and function conversion checking) should succeed with a >>>>> `shared` / `owned` mismatch. If a function with an `owned` >>>>> parameter is converted to (or overrides) a function with a >>>>> `shared` parameter, the argument type must actually be >>>>> copyable. >>>>> >>>>> - A method can be explicitly declared `consuming`. >>>>> >>>>> ``` >>>>> consuming func moveElements(into collection: inout [Element]) { >>>>> ... >>>>> } >>>>> ``` >>>>> >>>>> This causes `self` to be passed as an owned value and therefore >>>>> cannot be combined with `mutating` or `nonmutating`. >>>>> >>>>> `self` is still an immutable binding within the method. >>>>> >>>>> ### Function results >>>>> >>>>> As discussed at the start of this section, Swift's lexical >>>>> access model does not extend well to allowing ephemerals >>>>> to be returned from functions. Performing an access requires >>>>> executing storage-specific code at both the beginning and >>>>> the end of the access. After a function returns, it has no >>>>> further ability to execute code. >>>>> >>>>> We could, conceivably, return a callback along with the >>>>> ephemeral, with the expectation that the callback will be >>>>> executed when the caller is done with the ephemeral. >>>>> However, this alone would not be enough, because the callee >>>>> might be relying on guarantees from its caller. For example, >>>>> considering a `mutating` method on a `struct` which wants >>>>> to returns an `inout` reference to a stored property. The >>>>> correctness of this depends not only on the method being able >>>>> to clean up after the access to the property, but on the >>>>> continued validity of the variable to which `self` was bound. >>>>> What we really want is to maintain the current context in the >>>>> callee, along with all the active scopes in the caller, and >>>>> simply enter a new nested scope in the caller with the >>>>> ephemeral as a sort of argument. But this is a well-understood >>>>> situation in programming languages: it is just a kind of >>>>> co-routine. (Because of the scoping restrictions, it can >>>>> also be thought of as sugar for a callback function in >>>>> which `return`, `break`, etc. actually work as expected.) >>>>> >>>>> In fact, co-routines are useful for solving a number of >>>>> problems relating to ephemerals. We will explore this >>>>> idea in the next few sub-sections. >>>>> >>>>> ### `for` loops >>>>> >>>>> In the same sense that there are three interesting ways of >>>>> passing an argument, we can identify three interesting styles >>>>> of iterating over a sequence. Each of these can be expressed >>>>> with a `for` loop. >>>>> >>>>> #### Consuming iteration >>>>> >>>>> The first iteration style is what we're already familiar with >>>>> in Swift: a consuming iteration, where each step is presented >>>>> with an owned value. This is the only way we can iterate over >>>>> an arbitrary sequence where the values might be created on demand. >>>>> It is also important for working with collections of non-copyable >>>>> types because it allows the collection to be destructured and the >>>>> loop to take ownership of the elements. Because it takes ownership >>>>> of the values produced by a sequence, and because an arbitrary >>>>> sequence cannot be iterated multiple times, this is a >>>>> `consuming` operation on `Sequence`. >>>>> >>>>> This can be explicitly requested by declaring the iteration >>>>> variable `owned`: >>>>> >>>>> ``` >>>>> for owned employee in company.employees { >>>>> newCompany.employees.append(employee) >>>>> } >>>>> ``` >>>>> >>>>> It is also used implicitly when the requirements for a >>>>> non-mutating iteration are not met. (Among other things, >>>>> this is necessary for source compatibility.) >>>>> >>>>> The next two styles make sense only for collections. >>>>> >>>>> #### Non-mutating iteration >>>>> >>>>> A non-mutating iteration simply visits each of the elements >>>>> in the collection, leaving it intact and unmodified. We have >>>>> no reason to copy the elements; the iteration variable can >>>>> simply be bound to a shared value. This is a `nonmutating` >>>>> operaton on `Collection`. >>>>> >>>>> This can be explicitly requested by declaring the iteration >>>>> variable `shared`: >>>>> >>>>> ``` >>>>> for shared employee in company.employees { >>>>> if !employee.respected { throw CatastrophicHRFailure() } >>>>> } >>>>> ``` >>>>> >>>>> It is also used by default when the sequence type is known to >>>>> conform to `Collection`, since this is the optimal way of >>>>> iterating over a collection. >>>>> >>>>> ``` >>>>> for employee in company.employees { >>>>> if !employee.respected { throw CatastrophicHRFailure() } >>>>> } >>>>> ``` >>>>> >>>>> If the sequence operand is a storage reference expression, >>>>> the storage is accessed for the duration of the loop. Note >>>>> that this means that the Law of Exclusivity will implicitly >>>>> prevent the collection from being modified during the >>>>> iteration. Programs can explicitly request an iteration >>>>> over a copy of the value in that storage by using the `copy` >>>>> intrinsic function on the operand. >>>>> >>>>> #### Mutating iteration >>>>> >>>>> A mutating iteration visits each of the elements and >>>>> potentially changes it. The iteration variable is an >>>>> `inout` reference to the element. This is a `mutating` >>>>> operation on `MutableCollection`. >>>>> >>>>> This must be explicitly requested by declaring the >>>>> iteration variable `inout`: >>>>> >>>>> ``` >>>>> for inout employee in company.employees { >>>>> employee.respected = true >>>>> } >>>>> ``` >>>>> >>>>> The sequence operand must be a storage reference expression. >>>>> The storage will be accessed for the duraton of the loop, >>>>> which (as above) will prevent any other overlapping accesses >>>>> to the collection. (But this rule does not apply if the >>>>> collection type defines the operation as a non-mutating >>>>> operation, which e.g. a reference-semantics collection might.) >>>>> >>>>> #### Expressing mutating and non-mutating iteration >>>>> >>>>> Mutating and non-mutating iteration require the collection >>>>> to produce ephemeral values at each step. There are several >>>>> ways we could express this in the language, but one reasonable >>>>> approach would be to use co-routines. Since a co-routine does >>>>> not abandon its execution context when yielding a value to its >>>>> caller, it is reasonable to allow a co-routine to yield >>>>> multiple times, which corresponds very well to the basic >>>>> code pattern of a loop. This produces a kind of co-routine >>>>> often called a generator, which is used in several major >>>>> languages to conveniently implement iteration. In Swift, >>>>> to follow this pattern, we would need to allow the definition >>>>> of generator functions, e.g.: >>>>> >>>>> ``` >>>>> mutating generator iterateMutable() -> inout Element { >>>>> var i = startIndex, e = endIndex >>>>> while i != e { >>>>> yield &self[i] >>>>> self.formIndex(after: &i) >>>>> } >>>>> } >>>>> ``` >>>>> >>>>> On the client side, it is clear how this could be used to >>>>> implement `for` loops; what is less clear is the right way to >>>>> allow generators to be used directly by code. There are >>>>> interesting constraints on how the co-routine can be used >>>>> here because, as mentioned above, the entire co-routine >>>>> must logically execute within the scope of an access to the >>>>> base value. If, as is common for generators, the generator >>>>> function actually returns some sort of generator object, >>>>> the compiler must ensure that that object does not escape >>>>> that enclosing access. This is a significant source of >>>>> complexity. >>>>> >>>>> ### Generalized accessors >>>>> >>>>> Swift today provides very coarse tools for implementing >>>>> properties and subscripts: essentially, just `get` and `set` >>>>> methods. These tools are inadequate for tasks where >>>>> performance is critical because they don't allow direct >>>>> access to values without copies. The standard library >>>>> has access to a slightly broader set of tools which can >>>>> provide such direct access in limited cases, but they're >>>>> still quite weak, and we've been reluctant to expose them to >>>>> users for a variety of reasons. >>>>> >>>>> Ownership offers us an opportunity to revisit this problem >>>>> because `get` doesn't work for collections of non-copyable >>>>> types because it returns a value, which must therefore be >>>>> owned. The accessor really needs to be able to yield a >>>>> shared value instead of returning an owned one. Again, >>>>> one reasonable approach for allowing this is to use a >>>>> special kind of co-routine. Unlike a generator, this >>>>> co-routine would be required to yield exactly once. >>>>> And there is no need to design an explicit way for programmers >>>>> to invoke one because these would only be used in accessors. >>>>> >>>>> The idea is that, instead of defining `get` and `set`, >>>>> a storage declaration could define `read` and `modify`: >>>>> >>>>> ``` >>>>> var x: String >>>>> var y: String >>>>> var first: String { >>>>> read { >>>>> if x < y { yield x } >>>>> else { yield y } >>>>> } >>>>> modify { >>>>> if x < y { yield &x } >>>>> else { yield &y } >>>>> } >>>>> } >>>>> ``` >>>>> >>>>> A storage declaration must define either a `get` or a `read` >>>>> (or be a stored property), but not both. >>>>> >>>>> To be mutable, a storage declaration must also define either a >>>>> `set` or a `modify`. It may also choose to define *both*, in >>>>> which case `set` will be used for assignments and `modify` will >>>>> be used for modifications. This is useful for optimizing >>>>> certain complex computed properties because it allows modifications >>>>> to be done in-place without forcing simple assignments to first >>>>> read the old value; however, care must be taken to ensure that >>>>> the `modify` is consistent in behavior with the `get` and the >>>>> `set`. >>>>> >>>>> ### Intrinsic functions >>>>> >>>>> #### `move` >>>>> >>>>> The Swift optimizer will generally try to move values around >>>>> instead of copying them, but it can be useful to force its hand. >>>>> For this reason, we propose the `move` function. Conceptually, >>>>> `move` is simply a top-level function in the Swift standard >>>>> library: >>>>> >>>>> ``` >>>>> func move<T>(_ value: T) -> T { >>>>> return value >>>>> } >>>>> ``` >>>>> >>>>> However, it is blessed with some special semantics. It cannot >>>>> be used indirectly. The argument expression must be a reference >>>>> to some kind of local owning storage: either a `let`, a `var`, >>>>> or an `inout` binding. A call to `move` is evaluated by >>>>> semantically moving the current value out of the argument >>>>> variable and returning it as the type of the expression, >>>>> leaving the variable uninitialized for the purposes of the >>>>> definitive-initialization analysis. What happens with the >>>>> variable next depends on the kind of variable: >>>>> >>>>> - A `var` simply transitions back to being uninitialized. >>>>> Uses of it are illegal until it is assigned a new value and >>>>> thus reinitialized. >>>>> >>>>> - An `inout` binding is just like a `var`, except that it is >>>>> illegal for it to go out of scope uninitialized. That is, >>>>> if a program moves out of an `inout` binding, the program >>>>> must assign a new value to it before it can exit the scope >>>>> in any way (including by throwing an error). Note that the >>>>> safety of leaving an `inout` temporarily uninitialized >>>>> depends on the Law of Exclusivity. >>>>> >>>>> - A `let` cannot be reinitialized and so cannot be used >>>>> again at all. >>>>> >>>>> This should be a straightforward addition to the existing >>>>> definitive-initialization analysis, which proves that local >>>>> variables are initialized before use. >>>>> >>>>> #### `copy` >>>>> >>>>> `copy` is a top-level function in the Swift standard library: >>>>> >>>>> ``` >>>>> func copy<T>(_ value: T) -> T { >>>>> return value >>>>> } >>>>> ``` >>>>> >>>>> The argument must be a storage reference expression. The >>>>> semantics are exactly as given in the above code: the argument >>>>> value is returned. This is useful for several reasons: >>>>> >>>>> - It suppresses syntactic special-casing. For example, as >>>>> discussed above, if a `shared` argument is a storage >>>>> reference, that storage is normally accessed for the duration >>>>> of the call. The programmer can suppress this and force the >>>>> copy to complete before the call by calling `copy` on the >>>>> storage reference before >>>>> >>>>> - It is necessary for types that have suppressed implicit >>>>> copies. See the section below on non-copyable types. >>>>> >>>>> #### `endScope` >>>>> >>>>> `endScope` is a top-level function in the Swift standard library: >>>>> >>>>> ``` >>>>> func endScope<T>(_ value: T) -> () {} >>>>> ``` >>>>> >>>>> The argument must be a reference to a local `let`, `var`, or >>>>> standalone (non-parameter, non-loop) `inout` or `shared` >>>>> declaration. If it's a `let` or `var`, the variable is >>>>> immediately destroyed. If it's an `inout` or `shared`, >>>>> the access immediately ends. >>>>> >>>>> The definitive-initialization analysis must prove that >>>>> the declaration is not used after this call. It's an error >>>>> if the storage is a `var` that's been captured in an escaping >>>>> closure. >>>>> >>>>> This is useful for ending an access before control reaches the >>>>> end of a scope, as well as for micro-optimizing the destruction >>>>> of values. >>>>> >>>>> `endScope` provides a guarantee that the given variable has >>>>> been destroyed, or the given access has ended, by the time >>>>> of the execution of the call. It does not promise that these >>>>> things happen exactly at that point: the implementation is >>>>> still free to end them earlier. >>>>> >>>>> ### Lenses >>>>> >>>>> Currently, all storage reference expressions in Swift are *concrete*: >>>>> every component is statically resolvable to a storage declaration. >>>>> There is some recurring interest in the community in allowing programs >>>>> to abstract over storage, so that you might say: >>>>> >>>>> ``` >>>>> let prop = Widget.weight >>>>> ``` >>>>> >>>>> and then `prop` would be an abstract reference to the `weight` >>>>> property, and its type would be something like `(Widget) -> Double`. >>>>> >>>>> This feature is relevant to the ownership model because an ordinary >>>>> function result must be an owned value: not shared, and not mutable. >>>>> This means lenses could only be used to abstract *reads*, not >>>>> *writes*, and could only be created for copyable properties. It >>>>> also means code using lenses would involve more copies than the >>>>> equivalent code using concrete storage references. >>>>> >>>>> Suppose that, instead of being simple functions, lenses were their >>>>> own type of value. An application of a lens would be a storage >>>>> reference expression, but an *abstract* one which accessed >>>>> statically-unknown members. This would require the language >>>>> implementation to be able to perform that sort of access >>>>> dynamically. However, the problem of accessing an unknown >>>>> property is very much like the problem of accessing a known >>>>> property whose implementation is unknown; that is, the language >>>>> already has to do very similar things in order to implement >>>>> generics and resilience. >>>>> >>>>> Overall, such a feature would fit in very neatly with the >>>>> ownership model laid out here. >>>>> >>>>> ## Non-copyable types >>>>> >>>>> Non-copyable types are useful in a variety of expert situations. >>>>> For example, they can be used to efficiently express unique >>>>> ownership. They are also interesting for expressing values >>>>> that have some sort of independent identity, such as atomic >>>>> types. They can also be used as a formal mechanism for >>>>> encouraging code to work more efficiently with types that >>>>> might be expensive to copy, such as large struct types. The >>>>> unifying theme is that we do not want to allow the type to >>>>> be copied implicitly. >>>>> >>>>> The complexity of handling non-copyable types in Swift >>>>> comes from two main sources: >>>>> >>>>> - The language must provide tools for moving values around >>>>> and sharing them without forcing copies. We've already >>>>> proposed these tools in this document because they're >>>>> equally important for optimizing the use of copyable types. >>>>> >>>>> - The generics system has to be able to express generics over >>>>> non-copyable types without massively breaking source >>>>> compatibility and forcing non-copyable types on everybody. >>>>> >>>>> Otherwise, the feature itself is pretty small. The compiler >>>>> implicitly emits moves instead of copies, just like we discussed >>>>> above for the `move` intrinsic, and then diagnoses anything >>>>> that that didn't work for. >>>>> >>>>> ### `moveonly` contexts >>>>> >>>>> The generics problem is real, though. The most obvious way >>>>> to model copyability in Swift is to have a `Copyable` >>>>> protocol which types can conform to. An unconstrained type >>>>> parameter `T` would then not be assumed to be copyable. >>>>> Unfortunately, this would be a disaster for both source >>>>> compatibility and usability, because almost all the existing >>>>> generic code written in Swift assumes copyability, and >>>>> we really don't want programmers to have to worry about >>>>> non-copyable types in their first introduction to generic >>>>> code. >>>>> >>>>> Furthermore, we don't want types to have to explicitly >>>>> declare conformance to `Copyable`. That should be the >>>>> default. >>>>> >>>>> The logical solution is to maintain the default assumption >>>>> that all types are copyable, and then allow select contexts >>>>> to turn that assumption off. We will cause these contexts >>>>> `moveonly` contexts. All contexts lexically nested within >>>>> a `moveonly` context are also implicitly `moveonly`. >>>>> >>>>> A type can be a `moveonly` context: >>>>> >>>>> ``` >>>>> moveonly struct Array<Element> { >>>>> // Element and Array<Element> are not assumed to be copyable here >>>>> } >>>>> ``` >>>>> >>>>> This suppresses the `Copyable` assumption for the type >>>>> declared, its generic arguments (if any), and their >>>>> hierarchies of associated types. >>>>> >>>>> An extension can be a `moveonly` context: >>>>> >>>>> ``` >>>>> moveonly extension Array { >>>>> // Element and Array<Element> are not assumed to be copyable here >>>>> } >>>>> ``` >>>>> >>>>> A type can declare conditional copyability using a conditional >>>>> conformance: >>>>> >>>>> ``` >>>>> moveonly extension Array: Copyable where Element: Copyable { >>>>> ... >>>>> } >>>>> ``` >>>>> >>>>> Conformance to `Copyable`, conditional or not, is an >>>>> inherent property of a type and must be declared in the >>>>> same module that defines the type. (Or possibly even the >>>>> same file.) >>>>> >>>>> A non-`moveonly` extension of a type reintroduces the >>>>> copyability assumption for the type and its generic >>>>> arguments. This is necessary in order to allow standard >>>>> library types to support non-copyable elements without >>>>> breaking compatibility with existing extensions. If the >>>>> type doesn't declare any conformance to `Copyable`, giving >>>>> it a non-`moveonly` extension is an error. >>>>> >>>>> A function can be a `moveonly` context: >>>>> >>>>> ``` >>>>> extension Array { >>>>> moveonly func report<U>(_ u: U) >>>>> } >>>>> ``` >>>>> >>>>> This suppresses the copyability assumption for any new >>>>> generic arguments and their hierarchies of associated types. >>>>> >>>>> A lot of the details of `moveonly` contexts are still up >>>>> in the air. It is likely that we will need substantial >>>>> implementation experience before we can really settle on >>>>> the right design here. >>>>> >>>>> One possibility we're considering is that `moveonly` >>>>> contexts will also suppress the implicit copyability >>>>> assumption for values of copyable types. This would >>>>> provide an important optimization tool for code that >>>>> needs to be very careful about copies. >>>>> >>>>> ### `deinit` for non-copyable types >>>>> >>>>> A value type declared `moveonly` which does not conform >>>>> to `Copyable` (even conditionally) may define a `deinit` >>>>> method. `deinit` must be defined in the primary type >>>>> definition, not an extension. >>>>> >>>>> `deinit` will be called to destroy the value when it is >>>>> no longer required. This permits non-copyable types to be >>>>> used to express the unique ownership of resources. For >>>>> example, here is a simple file-handle type that ensures >>>>> that the handle is closed when the value is destroyed: >>>>> >>>>> ``` >>>>> moveonly struct File { >>>>> var descriptor: Int32 >>>>> >>>>> init(filename: String) throws { >>>>> descriptor = Darwin.open(filename, O_RDONLY) >>>>> >>>>> // Abnormally exiting 'init' at any point prevents deinit >>>>> // from being called. >>>>> if descriptor == -1 { throw ... } >>>>> } >>>>> >>>>> deinit { >>>>> _ = Darwin.close(descriptor) >>>>> } >>>>> >>>>> consuming func close() throws { >>>>> if Darwin.fsync(descriptor) != 0 { throw ... } >>>>> >>>>> // This is a consuming function, so it has ownership of self. >>>>> // It doesn't consume self in any other way, so it will >>>>> // destroy it when it exits by calling deinit. deinit >>>>> // will then handle actually closing the descriptor. >>>>> } >>>>> } >>>>> ``` >>>>> >>>>> Swift is permitted to destroy a value (and thus call `deinit`) >>>>> at any time between its last use and its formal point of >>>>> destruction. The exact definition of "use" for the purposes >>>>> of this definition is not yet fully decided. >>>>> >>>>> If the value type is a `struct`, `self` can only be used >>>>> in `deinit` in order to refer to the stored properties >>>>> of the type. The stored properties of `self` are treated >>>>> like local `let` constants for the purposes of the >>>>> definitive-initialization analysis; that is, they are owned >>>>> by the `deinit` and can be moved out of. >>>>> >>>>> If the value type is an `enum`, `self` can only be used >>>>> in `deinit` as the operand of a `switch`. Within this >>>>> `switch`, any associated values are used to initialize >>>>> the corresponding bindings, which take ownership of those >>>>> values. Such a `switch` leaves `self` uninitialized. >>>>> >>>>> ### Explicitly-copyable types >>>>> >>>>> Another idea in the area of non-copyable types that we're >>>>> exploring is the ability to declare that a type cannot >>>>> be implicitly copied. For example, a very large struct >>>>> can formally be copied, but it might be an outsized >>>>> impact on performance if it is copied unnecessarily. >>>>> Such a type should conform to `Copyable`, and it should >>>>> be possible to request a copy with the `copy` function, >>>>> but the compiler should diagnose any implicit copies >>>>> the same way that it would diagnose copies of a >>>>> non-copyable type. >>>>> >>>>> ## Implementation priorities >>>>> >>>>> This document has laid out a large amount of work. >>>>> We can summarize it as follows: >>>>> >>>>> - Enforcing the Law of Exclusivity: >>>>> >>>>> - Static enforcement >>>>> - Dynamic enforcement >>>>> - Optimization of dynamic enforcement >>>>> >>>>> - New annotations and declarations: >>>>> >>>>> - `shared` parameters >>>>> - `consuming` methods >>>>> - Local `shared` and `inout` declarations >>>>> >>>>> - New intrinsics affecting DI: >>>>> >>>>> - The `move` function and its DI implications >>>>> - The `endScope` function and its DI implications >>>>> >>>>> - Co-routine features: >>>>> >>>>> - Generalized accessors >>>>> - Generators >>>>> >>>>> - Non-copyable types >>>>> >>>>> - Further design work >>>>> - DI enforcement >>>>> - `moveonly` contexts >>>>> >>>>> The single most important goal for the upcoming releases is >>>>> ABI stability. The prioritization and analysis of these >>>>> features must center around their impact on the ABI. With >>>>> that in mind, here are the primary ABI considerations: >>>>> >>>>> The Law of Exclusivity affects the ABI because it >>>>> changes the guarantees made for parameters. We must adopt >>>>> this rule before locking down on the ABI, or else we will >>>>> get stuck making conservative assumptions forever. >>>>> However, the details of how it is enforced do not affect >>>>> the ABI unless we choose to offload some of the work to the >>>>> runtime, which is not necessary and which can be changed >>>>> in future releases. (As a technical note, the Law >>>>> of Exclusivity is likely to have a major impact on the >>>>> optimizer; but this is an ordinary project-scheduling >>>>> consideration, not an ABI-affecting one.) >>>>> >>>>> The standard library is likely to enthusiastically adopt >>>>> ownership annotations on parameters. Those annotations will >>>>> affect the ABI of those library routines. Library >>>>> developers will need time in order to do this adoption, >>>>> but more importantly, they will need some way to validate >>>>> that their annotations are useful. Unfortunately, the best >>>>> way to do that validation is to implement non-copyable types, >>>>> which are otherwise very low on the priority list. >>>>> >>>>> The generalized accessors work includes changing the standard >>>>> set of "most general" accessors for properties and subscripts >>>>> from `get`/`set`/`materializeForSet` to (essentially) >>>>> `read`/`set`/`modify`. This affects the basic ABI of >>>>> all polymorphic property and subscript accesses, so it >>>>> needs to happen. However, this ABI change can be done >>>>> without actually taking the step of allowing co-routine-style >>>>> accessors to be defined in Swift. The important step is >>>>> just ensuring that the ABI we've settled on is good >>>>> enough for co-routines in the future. >>>>> >>>>> The generators work may involve changing the core collections >>>>> protocols. That will certainly affect the ABI. In contrast >>>>> with the generalized accessors, we will absolutely need >>>>> to implement generators in order to carry this out. >>>>> >>>>> Non-copyable types and algorithms only affect the ABI >>>>> inasmuch as they are adopted in the standard library. >>>>> If the library is going to extensively adopt them for >>>>> standard collections, that needs to happen before we >>>>> stabilize the ABI. >>>>> >>>>> The new local declarations and intrinsics do not affect the ABI. >>>>> (As is often the case, the work with the fewest implications >>>>> is also some of the easiest.) >>>>> >>>>> Adopting ownership and non-copyable types in the standard >>>>> library is likely to be a lot of work, but will be important >>>>> for the usability of non-copyable types. It would be very >>>>> limiting if it was not possible to create an `Array` >>>>> of non-copyable types. >>>>> >>>>> (end) >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >> > -- Intersec <http://www.intersec.com> Florent Bruneau | Chief Scientist E-Mail: florent.brun...@intersec.com Mob: +33 (0)6 01 02 65 57 Tour W - 102 Terrasse Boieldieu 92085 Paris La Défense - Cedex France _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution