> 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

Reply via email to