[swift-evolution] [Proposal] Scoped resources (like C# using statement)

Kevin Ballard kevin at sb.org
Tue Dec 29 22:55:15 CST 2015

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).

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 swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151229/eed27746/attachment.html>

More information about the swift-evolution mailing list