> On Dec 29, 2015, at 8:55 PM, Kevin Ballard via swift-evolution
> <[email protected]> wrote:
>
> An alternative solution is to do what Rust and C++ do, which is to use RAII.
> Which is to say, instead of introducing a new language construct that's
> explicitly tied to a scope, you just use a struct to represent the resource
> that you hold (e.g. a File that represents an open file). Of course, this
> does require some changes to structs, notably the addition of a deinit. And
> if structs have a deinit, then they also need to have a way to restrict
> copies. This is precisely what Rust does; any struct in Rust that implements
> Drop (the equivalent to deinit) loses the ability to be implicitly copied (a
> second trait called Clone provides a .clone() method that is the normal way
> to copy such non-implicitly-copyable structs).
deinit doesn't make sense for value types. Classes already support deinit, and
you can use withExtendedLifetime to bound the lifetime of a resource-holding
class. It would be reasonable to have a scoped lifetime marker similar to ObjC
ARC too.
-Joe
> This solution is elegant for a few reasons:
>
> 1. Once you allow deinit on structs (which is useful) and deal with the fact
> that structs are no longer implicitly copyable (because almost all deinit
> functions on structs won't work right if they're called twice, such as on two
> copies), then RAII just sort of falls out of all of this and doesn't require
> any specific language features.
> 2. It's more flexible, because you can actually return the RAII value from a
> scope in order to extend its lifetime.
> 3. The RAII value itself provides the means to access the resource it's
> protecting; e.g. a Lock might return a LockGuard RAII value from the .lock()
> method, and LockGuard provides the means to access the protected value (as
> opposed to just having the lock sitting next to the value, which makes it
> trivially easy to accidentally access the value without holding the lock).
> 4. In Rust, this pattern also integrates extremely well with Rust's lifetime
> system (the system that prevents data races / memory corruption at compile
> time), because e.g. a LockGuard contains the lifetime of the Lock, which
> prevents you at compile-time from attempting to lock the Lock while you
> already have it locked (though it doesn't prevent deadlocks where you and
> another thread try and lock two locks in opposite orders, but there is plenty
> of stuff it does catch).
>
> The biggest problem with adding deinit to structs in Swift right now is the
> fact that we don't have references, which means you can't take a RAII struct
> and pass it to another function without losing it. Heck, you can't even call
> a method on it, because `self` on non-mutating methods in Swift is a value
> and not a reference (although we could hand-wave that away and make `self` in
> non-mutating RAII methods actually be an "in" reference internally, but this
> kind of hand-waving doesn't work when passing the struct as an argument to
> another function). So we'd actually have to introduce a special "in"
> reference, which would be like "inout" except it doesn't actually copy it out
> (and is guaranteed to actually pass a pointer, although this pointer may be a
> pointer to a temporary). Except even that fails if you want to have a
> computed property that returns an existing RAII value (for example, having an
> Array of these things; barring optimizations, the subscript getter returns a
> computed value). And you can't generalize such "in" references beyond
> function arguments without basically providing raw C pointers. Rust's
> lifetime system lets them do it safely, but barring such a system, Swift
> can't really do this (I assume it's obvious why we don't want to start using
> raw C pointers everywhere).
>
> All that said, I think this is a problem Swift needs to solve, because having
> non-copyable structs would be very useful. In particular, I _really_ want
> some way to do atomics in Swift, but the only safe way I can think of to do
> it requires non-copyable structs (because it's not correct to do a nonatomic
> read (such as a memcpy) of an atomic that's visible to other threads). I
> suppose you could provide a way to override how struct copies work (e.g. so
> you could do an atomic read of the old value), but it's rather problematic to
> break the assumption that struct copies are cheap.
>
> Of course, you could model RAII with classes instead of structs, that just
> has the overhead of heap allocation (+ atomic reference counting) for every
> RAII value.
>
> -Kevin Ballard
>
> On Tue, Dec 29, 2015, at 08:02 PM, Trent Nadeau via swift-evolution wrote:
>> # Introduction
>>
>> Add a new `Scoped` protocol and enhance the do statement to automatically
>> call enter/exit actions on resources.
>>
>> # Motivation
>>
>> Resources (e.g., locks, files, sockets, etc.) often need to be scoped to a
>> block, where some action is taken at the start of the block and another is
>> required at the end. Examples include locking and unlocking a lock in a
>> critical section or closing a file at the end of a block.
>>
>> Doing this manually is possible using `defer` statements among other
>> options, but this is error prone as a `defer` can be forgotten,
>> `lock`/`unlock` calls for two locks can be switched due to a typo, etc.
>> Having a dedicated language construct for this common case makes it easier
>> to read and write while making code shorter and clearer.
>>
>> # Language Survey
>>
>> At least three major languages have widely used statements for this use case.
>>
>> ## C#
>>
>> C# has the `using` statement and the associated `IDisposable` interface.
>>
>> ```csharp
>> using (StreamReader sr = new StreamReader(filename)) {
>> txt = sr.ReadToEnd();
>> }
>> ```
>>
>> C#'s solution only handles close/exit actions via the
>> `IDisposable.Dispose()` method and so cannot easily be used with items such
>> as locks; however, C# has the additional `lock` statement for that use case.
>>
>> ## Java
>>
>> Java has try-with-resources and the associated `AutoCloseable` interface.
>>
>> ```java
>> try (BufferedReader br = new BufferedReader(new FileReader(path))) {
>> return br.readLine();
>> }
>> ```
>>
>> Java's solution only handles close/exit actions via the
>> `AutoCloseable.close()` method and so cannot easily be used with items such
>> as locks; however, Java has the additional `synchronized` statement for that
>> use case.
>>
>> ## Python
>>
>> Python has with `with` statement and the associated `__enter__` and
>> `__exit__` special methods that classes may implement to become a "context
>> manager".
>>
>> ```python
>> with lock, open(path) as my_file:
>> contents = my_file.read()
>> # use contents
>> ```
>>
>> Python's solution handles both enter and exit actions and so this one
>> construct is usable for locks as well as resources like sockets and files.
>>
>> # Proposed Solution
>>
>> We add a new protocol called `Scoped` to the standard library. Types for
>> resources that have enter/exit actions will be extended to add conformance
>> to this protocol.
>>
>> The `do` statement will be extended to allow a new `using <resources>`
>> "suffix".
>>
>> # Detailed Design
>>
>> The `Scoped` protocol shall be as follows:
>>
>> ```swift
>> public protocol Scoped {
>> func enterScope()
>> func exitScope()
>> }
>> ```
>>
>> The compiler statement will accept a new form for resources. For example,
>>
>> ```swift
>> do using lock, let file = try getFileHandle() {
>> // statements
>> }
>> ```
>>
>> As can be seen in the example above, the resources can be bindings that
>> already exist (like `lock`) or can be new bindings. Bindings created with
>> `do using` are not available outside of the scope of the `do using`. Only
>> types conforming to `Scoped` may be using with `do using`. Use of
>> non-conforming types will result in a compiler error.
>>
>> The above example would be syntactic sugar for the following:
>>
>> ```swift
>> do {
>> lock.enterScope()
>> defer { lock.exitScope() }
>>
>> let file = try getFileHandle()
>> file.enterScope()
>> defer { file.exitScope() }
>>
>> // statements
>> }
>> ```
>>
>> # Framework Changes / Examples
>>
>> As an example of some real-world classes that would be useful with `Scoped`,
>> from Foundation:
>>
>> ```swift
>> // Would be nice to extend the NSLocking protocol instead, but that's not
>> supported yet.
>> extension NSLock: Scoped {
>> func enterScope() {
>> self.lock()
>> }
>>
>> func exitScope() {
>> self.unlock()
>> }
>> }
>>
>> extension NSFileHandle: Scoped {
>> func enterScope() {}
>>
>> func exitScope() {
>> self.closeFile()
>> }
>> }
>> ```
>>
>> # Questions and Concerns
>> * Bikeshedding protocol naming and scoping syntax
>> * Should the enter and exit actions be allowed to throw errors?
>>
>> --
>> Trent Nadeau
>>
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>
>
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution