<p>There are various external resources, like file handles, TCP connections, and various servers, which need to be closed in some specific way before being dropped. There are a few approaches to these that I've seen:</p>
<ol>
<li>Just leave it to the programmer to properly close the resource</li>
<li>Provide support for automatically running a <code>close</code> function when the struct/object representing the resource is dropped, or in GC'd languages, a finalizer that's guaranteed to run (and push library designers to do this)</li>
<li>Disallow automatic dropping/garbage collection of some custom types, and throw a compile-time warning if this would occur (like Rust with <code>Result</code>), such that library designers can make running <code>close</code> the only way to drop the resource from the outside</li>
<li>Provide (and require the use of) a construct like Python's <code>with</code> that explicitly scopes the &quot;lifetime&quot; of the resource, also forcing the programmer to acknowledge the resource needs to be <code>close</code>d before dropping it</li>
</ol>
<p>What are the pros and cons of these approaches, or any I've missed?</p>

<garbage-collection><compiler-checks><side-effects>
What are the pros and cons of different ways of forcing resources to be closed properly?
CC BY-SA 4.0
2023-07-03T20:01:01.487Z
Does Swift [`defer`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement) count?
Bbrk24
CC BY-SA 4.0
2023-07-03T20:02:32.337Z
@Bbrk24 I think that's more of a more convenient way to do #1, rather than a way of forcing the programmer to properly close resources
rydwolf
CC BY-SA 4.0
2023-07-03T20:23:31.173Z
5. Rely on the garbage collector to call a finalizer.  May not work correctly in languages with non-deterministic garbage collection.
dan04
CC BY-SA 4.0
2023-07-03T20:24:36.057Z
[Related question from Software Engineering](https://softwareengineering.stackexchange.com/questions/231789/disadvantages-of-scoped-based-memory-management)
dan04
CC BY-SA 4.0
2023-07-03T20:26:45.160Z
You can drop a file without closing in rust entirely safely with `mem::forget`. It's considered safe in rust to never call `drop`
mousetail 'he-him'
CC BY-SA 4.0
2023-07-03T20:32:58.767Z
In general none of these "force" closing files, they just are convenient ways a programmer can choose to use to more ergonomically close things that should be closed.
mousetail 'he-him'
CC BY-SA 4.0
2023-07-03T20:36:44.677Z
@mousetail They force it to varying degrees. The second one basically forces it, even if you can work around that by causing a memory leak. Three "forces" it but allows you to ignore the warning. And four really does force it if the `with` is mandatory, which it would ideally be made to be.
rydwolf
CC BY-SA 4.0
2023-07-03T20:38:05.040Z
I don't know any language where with is mandatory, and in fact with can't be used in all situations so it would be very bad design to make it mandatory. I'll expand on this in my answer
mousetail 'he-him'
CC BY-SA 4.0
2023-07-03T20:39:06.787Z
@mousetail It's not really true that this is just a more convenient way to close things; most of these options also remove the possibility of the programmer forgetting something needs to be closed, or intending to close it, but accidentally missing one possible branch.
rydwolf
CC BY-SA 4.0
2023-07-03T20:46:01.477Z
@dan04 Isn't that just #2?
rydwolf
CC BY-SA 4.0
2023-07-03T20:49:14.323Z
@RydwolfPrograms: I had interpreted #2 as being like C++ destructors, which are scope-based and deterministic.  A garbage collector, OTOH, may or may not ever run object finalizers.
dan04
CC BY-SA 4.0
2023-07-03T20:50:41.780Z
@dan04 Ohh right, forgot GCs had that property. For #2 tho, when I say `drop` I meant to include finalizers run by GCs
rydwolf
CC BY-SA 4.0
2023-07-03T20:51:23.280Z
@RydwolfPrograms: You might want to edit your question to clarify that.
dan04
CC BY-SA 4.0
2023-07-03T20:55:44.330Z
@dan04 Done, lmk if it's still unclear!
rydwolf
<h1>Swift 5.9: noncopyable types</h1>
<p>In Swift as it stands today, all types are implicitly copyable. That means you can write code like this with an unconstrained generic:</p>
<pre class="lang-swift prettyprint-override"><code>func copy&lt;T&gt;(_ x: T) -&gt; (T, T) { (x, x) }
</code></pre>
<p>For these types, it's impossible to know when the lifetime ends -- every semantic copy is allowed (but not required) to be a literal copy, and this rampant copying means non-reference types don't have a notion of ownership.</p>
<p>Swift 5.9 will add &quot;non-copyable&quot; or &quot;move-only&quot; types. Your example of a file handle was the prime example for why this should be added:</p>
<pre class="lang-swift prettyprint-override"><code>struct FileDescriptor: ~Copyable {
  private var fd: Int32

  init(fd: Int32) { self.fd = fd }

  func write(buffer: Data) {
    buffer.withUnsafeBytes { 
      write(fd, $0.baseAddress!, $0.count)
    }
  }

  deinit {
    close(fd)
  }
}
</code></pre>
<p>These non-copyable types can only be <code>borrow</code>ed (aka &quot;shared&quot;) or <code>consume</code>d (aka &quot;moved&quot;), and aren't copied all over the place. This comes with <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0390-noncopyable-structs-and-enums.md#the-copyable-generic-constraint" rel="nofollow noreferrer">a lot of restrictions</a> to maintain backwards compatibility, but a language that had these from the outset wouldn't need them.</p>
<p>Because an instance of a non-copyable type can only be borrowed or moved, it's easy to tell where ownership lies: Are you borrowing it? If not, conglaturations, you own it. If an owned instance falls out of scope, its <code>deinit</code> runs. If a borrowed instance falls out of scope, its <code>deinit</code> does not run.</p>
<p>However, you don't always want to close the file when it falls out of scope -- maybe you're switching to a different model of the handle, and you need the file descriptor to stay open. For cases like that, there's the <code>discard</code> operator:</p>
<pre class="lang-swift prettyprint-override"><code>struct FileDescriptor: ~Copyable {
  // Take ownership of the C file descriptor away from this type,
  // returning the file descriptor without closing it
  consuming func take() -&gt; Int32 {
    let fd = self.fd
    discard self
    return fd
  }
}
</code></pre>
<p>Currently, the only valid way to use <code>discard</code> is <code>discard self</code>. It's equivalent to <code>mem::forget</code> in Rust or <code>GC.SuppressFinalize</code> in C#, but the fact that you can only use it on <code>self</code> means that users of your API can't unwittingly cause problems by leaking resources, unless they call a special method to transfer ownership.</p>

CC BY-SA 4.0
2023-07-03T20:27:36.790Z
2023-07-03T20:27:36.790Z
Bbrk24
<h3>unique_ptr all the way down</h3>
<p>One way to force the closing of resources is:</p>
<ol>
<li>Avoiding copies of references;</li>
<li>Keeping resources alive white there are references to it.</li>
</ol>
<p>Basically <code>std::unique_ptr</code>. Or the somewhat equivalent scope annotations in Rust.</p>
<h3>Opaque resources and native finalizers</h3>
<p>The &quot;be closed in some specific way before being dropped&quot; reminds me of PHP's resource management. The closing mechanism of external or native resources are never exposed. Resources appear on the code surface as an opaque handle or marker class instance.</p>
<p>When the instance handle is dropped on normal language, an native finalizer is executed.</p>

CC BY-SA 4.0
2023-07-04T15:57:45.623Z
2023-07-04T15:57:45.623Z
André L F S Bacci
<h2>The <code>with</code> statement approach is bad and should not be used for new languages</h2>
<p>The python <code>with</code> statement or the C# <code>using</code> statement work by allowing classes to implement some interface, then run some code at the end of a block whether there was an error or not. It's basically syntactic sugar for a <code>try</code>/<code>finally</code> block.</p>
<p>The main issue is, <strong>with can only be used if an object is created and destroyed in the same block</strong>. This means you can't safely store any closable object in a class or struct. Even returning a file from a function safely becomes impossible, creating a small gap where an error would result in the file not being properly closed.</p>
<p>Consider this code segment:</p>
<pre class="lang-py prettyprint-override"><code>def open_log_file():
    file = open(&quot;log.log&quot;, &quot;a&quot;)
    file.write(&quot;&gt; &quot;)
    return file

with open_log_file() as f:
    f.write(&quot;test completed&quot;)
</code></pre>
<p>Here, if the <code>file.write</code> inside the <code>open_log_file</code> throws an error, the file won't be closed. You could not wrap the function in a <code>with</code> statement because then it would be closed before the function could return. You could wrap the function in a <code>try/except</code> to close the file, but you need a significant amount of code to properly reconstruct and re-throw the error after closing the file.</p>
<p>Consider a more complex case with classes:</p>
<pre class="lang-py prettyprint-override"><code>class MultiFile:
    def __init__(self):
        self.file_a = open(&quot;a.txt&quot;)
        self.file_b = open(&quot;b.txt&quot;)
    
    def __enter__(self):
        return self
    def __exit__(self):
        self.file_a.close()
        self.file_b.close()

with MultiFile():
    pass
</code></pre>
<p>Here trying to implement the context manager interface is useless if the second file fails to open during the constructor.</p>
<p>Of course you could mess with many nested try-except blocks to catch every possible error but if you add a few more statements it quickly becomes extremely unwieldy to handle every case properly.</p>
<h3>Confession</h3>
<p>I have on spent days of my life tracking down bugs caused by trying to return the value used in a <code>with</code>/<code>using</code> statement from a function. Never again.</p>
<h3>With is also entirely superseded if you have any of the other techniques</h3>
<p>If you close a file when dropped, with has no effect.</p>
<p>If you don't have exceptions, then the entire statement has no effect and the risk it tries to mitigate is non-existant.</p>
<h3>Try/Finally is just as bad</h3>
<p>It's also impossibly to actually catch errors at every point with this syntax. The issue is not with <code>with</code> specifically but anything where the main context management system limited to a single block.</p>
<h3>It's confusing for beginners to have something that you &quot;should always use&quot; but doesn't work half the time</h3>
<p>Every python or C# tutorial emphasizes that you should always use files only inside a <code>with</code> statement. Yet in some situations it simply doesn't work. Explaining when it wouldn't work is actually hard, and depends on a deep understanding of how the return statement and exceptions work.</p>
<p>If we want to teach beginners a rigorous way to ensure robust code it would be better to have a simple mnemonic to teach them that always works.</p>
<p>If you choose not to use with it's really hard to verify the correctness of the code.</p>

CC BY-SA 4.0
2023-07-03T20:49:49.860Z
2023-07-04T04:20:55.703Z
2023-07-04T04:20:55.703Z
mousetail 'he-him'
CC BY-SA 4.0
2023-07-03T21:10:18.913Z
I don't follow this argument. `with` is a language construct which makes it easier to manage a closeable resource when its lifetime is logically restricted to a lexical block. For uses where the resource's lifetime is not logically restricted to a lexical block, you 
don't use `with`, you call `.close()` manually in the right place. Removing `with` from the language hurts in the former case and doesn't help in the latter case.
kaya3
CC BY-SA 4.0
2023-07-03T21:12:11.213Z
You write *"This means you can't safely store any closable object in a class or struct. Even returning a file from a function safely becomes impossible"*, but removing `with` from the language doesn't make those things safe, nor does it stop you from doing them unsafely.
kaya3
CC BY-SA 4.0
2023-07-04T00:10:09.947Z
There's a big difference between "`with` should not be the only way" and "`with` is bad". The vast majority of resource use fits the `with` model, so it's a good feature and has been provided in many languages (I first encountered it in Lisp's `WITH-OPEN-FILE`).
Barmar
CC BY-SA 4.0
2023-07-04T04:10:25.353Z
@Barmar Don't you understand my second example? It's nearly impossible to construct custom classes that safely implement the interface. That makes it worse than useless
mousetail 'he-him'
CC BY-SA 4.0
2023-07-04T13:56:56.380Z
@mousetail I was mainly addressing "with can only be used if an object is created and destroyed in the same block". That's the whole point of it.
Barmar
CC BY-SA 4.0
2023-07-04T13:59:48.453Z
That complex uses are hard to implement correctly doesn't seem like a good objection, unless there's something better. If you try to do "multifile" yourself, you run into the same try/except hell, so why not let one implementor figure it out and package it, rather than force everyone to do it (likely getting it wrong)?
Barmar
CC BY-SA 4.0
2024-12-21T20:19:53.673Z
@mousetail'he-him': I don't see any way to really fix the issues you're describing without adding means by which constructors could request notification if an exception is thrown between the instantiation of a new object and the exposure of the object to the code requesting its construction, and adding such means would solve the problems you're blaming on with/using constructs.  That's not to say the problems don't exist, but they're not the fault of with/using.
supercat
# What are the pros and cons of different ways of forcing resources to be closed properly?
Posted by: username_0 on 2023-07-03
There are various external resources, like file handles, TCP connections, and various servers, which need to be closed in some specific way before being dropped. There are a few approaches to these that I've seen:

1. Just leave it to the programmer to properly close the resource
2. Provide support for automatically running a `close` function when the struct/object representing the resource is dropped, or in GC'd languages, a finalizer that's guaranteed to run (and push library designers to do this)
3. Disallow automatic dropping/garbage collection of some custom types, and throw a compile-time warning if this would occur (like Rust with `Result`), such that library designers can make running `close` the only way to drop the resource from the outside
4. Provide (and require the use of) a construct like Python's `with` that explicitly scopes the "lifetime" of the resource, also forcing the programmer to acknowledge the resource needs to be `close`d before dropping it

What are the pros and cons of these approaches, or any I've missed?
**Tags:** garbage-collection compiler-checks side-effects
> **Comments:**
> **username_1**: Does Swift [`defer`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement) count?  
> **username_0**: @Bbrk24 I think that's more of a more convenient way to do #1, rather than a way of forcing the programmer to properly close resources  
> **username_2**: 5. Rely on the garbage collector to call a finalizer.  May not work correctly in languages with non-deterministic garbage collection.  
> **username_2**: [Related question from Software Engineering](https://softwareengineering.stackexchange.com/questions/231789/disadvantages-of-scoped-based-memory-management)  
> **username_3**: You can drop a file without closing in rust entirely safely with `mem::forget`. It's considered safe in rust to never call `drop`  
> **username_3**: In general none of these "force" closing files, they just are convenient ways a programmer can choose to use to more ergonomically close things that should be closed.  
> **username_0**: @mousetail They force it to varying degrees. The second one basically forces it, even if you can work around that by causing a memory leak. Three "forces" it but allows you to ignore the warning. And four really does force it if the `with` is mandatory, which it would ideally be made to be.  
> **username_3**: I don't know any language where with is mandatory, and in fact with can't be used in all situations so it would be very bad design to make it mandatory. I'll expand on this in my answer  
> **username_0**: @mousetail It's not really true that this is just a more convenient way to close things; most of these options also remove the possibility of the programmer forgetting something needs to be closed, or intending to close it, but accidentally missing one possible branch.  
> **username_0**: @dan04 Isn't that just #2?  
> **username_2**: @RydwolfPrograms: I had interpreted #2 as being like C++ destructors, which are scope-based and deterministic.  A garbage collector, OTOH, may or may not ever run object finalizers.  
> **username_0**: @dan04 Ohh right, forgot GCs had that property. For #2 tho, when I say `drop` I meant to include finalizers run by GCs  
> **username_2**: @RydwolfPrograms: You might want to edit your question to clarify that.  
> **username_0**: @dan04 Done, lmk if it's still unclear!  
---
## 3 answers
### Answer by username_1 on 2023-07-03. Score: 4
Swift 5.9: noncopyable types
In Swift as it stands today, all types are implicitly copyable. That means you can write code like this with an unconstrained generic:
func copy(_ x: T) -> (T, T) { (x, x) }

For these types, it's impossible to know when the lifetime ends -- every semantic copy is allowed (but not required) to be a literal copy, and this rampant copying means non-reference types don't have a notion of ownership.
Swift 5.9 will add "non-copyable" or "move-only" types. Your example of a file handle was the prime example for why this should be added:
struct FileDescriptor: ~Copyable {
  private var fd: Int32

  init(fd: Int32) { self.fd = fd }

  func write(buffer: Data) {
    buffer.withUnsafeBytes { 
      write(fd, $0.baseAddress!, $0.count)
    }
  }

  deinit {
    close(fd)
  }
}

These non-copyable types can only be borrowed (aka "shared") or consumed (aka "moved"), and aren't copied all over the place. This comes with a lot of restrictions to maintain backwards compatibility, but a language that had these from the outset wouldn't need them.
Because an instance of a non-copyable type can only be borrowed or moved, it's easy to tell where ownership lies: Are you borrowing it? If not, conglaturations, you own it. If an owned instance falls out of scope, its deinit runs. If a borrowed instance falls out of scope, its deinit does not run.
However, you don't always want to close the file when it falls out of scope -- maybe you're switching to a different model of the handle, and you need the file descriptor to stay open. For cases like that, there's the discard operator:
struct FileDescriptor: ~Copyable {
  // Take ownership of the C file descriptor away from this type,
  // returning the file descriptor without closing it
  consuming func take() -> Int32 {
    let fd = self.fd
    discard self
    return fd
  }
}

Currently, the only valid way to use discard is discard self. It's equivalent to mem::forget in Rust or GC.SuppressFinalize in C#, but the fact that you can only use it on self means that users of your API can't unwittingly cause problems by leaking resources, unless they call a special method to transfer ownership.
---
### Answer by username_4 on 2023-07-04. Score: 3
### unique\_ptr all the way down

One way to force the closing of resources is:

1. Avoiding copies of references;
2. Keeping resources alive white there are references to it.

Basically `std::unique_ptr`. Or the somewhat equivalent scope annotations in Rust.

### Opaque resources and native finalizers

The "be closed in some specific way before being dropped" reminds me of PHP's resource management. The closing mechanism of external or native resources are never exposed. Resources appear on the code surface as an opaque handle or marker class instance.

When the instance handle is dropped on normal language, an native finalizer is executed.
---
### Answer by username_3 on 2023-07-03. Score: 1
The with statement approach is bad and should not be used for new languages
The python with statement or the C# using statement work by allowing classes to implement some interface, then run some code at the end of a block whether there was an error or not. It's basically syntactic sugar for a try/finally block.
The main issue is, with can only be used if an object is created and destroyed in the same block. This means you can't safely store any closable object in a class or struct. Even returning a file from a function safely becomes impossible, creating a small gap where an error would result in the file not being properly closed.
Consider this code segment:
def open_log_file():
    file = open("log.log", "a")
    file.write("> ")
    return file

with open_log_file() as f:
    f.write("test completed")

Here, if the file.write inside the open_log_file throws an error, the file won't be closed. You could not wrap the function in a with statement because then it would be closed before the function could return. You could wrap the function in a try/except to close the file, but you need a significant amount of code to properly reconstruct and re-throw the error after closing the file.
Consider a more complex case with classes:
class MultiFile:
    def __init__(self):
        self.file_a = open("a.txt")
        self.file_b = open("b.txt")
    
    def __enter__(self):
        return self
    def __exit__(self):
        self.file_a.close()
        self.file_b.close()

with MultiFile():
    pass

Here trying to implement the context manager interface is useless if the second file fails to open during the constructor.
Of course you could mess with many nested try-except blocks to catch every possible error but if you add a few more statements it quickly becomes extremely unwieldy to handle every case properly.
Confession
I have on spent days of my life tracking down bugs caused by trying to return the value used in a with/using statement from a function. Never again.
With is also entirely superseded if you have any of the other techniques
If you close a file when dropped, with has no effect.
If you don't have exceptions, then the entire statement has no effect and the risk it tries to mitigate is non-existant.
Try/Finally is just as bad
It's also impossibly to actually catch errors at every point with this syntax. The issue is not with with specifically but anything where the main context management system limited to a single block.
It's confusing for beginners to have something that you "should always use" but doesn't work half the time
Every python or C# tutorial emphasizes that you should always use files only inside a with statement. Yet in some situations it simply doesn't work. Explaining when it wouldn't work is actually hard, and depends on a deep understanding of how the return statement and exceptions work.
If we want to teach beginners a rigorous way to ensure robust code it would be better to have a simple mnemonic to teach them that always works.
If you choose not to use with it's really hard to verify the correctness of the code.
> **Comments:**
> **username_5** (2023-07-03): I don't follow this argument. `with` is a language construct which makes it easier to manage a closeable resource when its lifetime is logically restricted to a lexical block. For uses where the resource's lifetime is not logically restricted to a lexical block, you 
don't use `with`, you call `.close()` manually in the right place. Removing `with` from the language hurts in the former case and doesn't help in the latter case.  
> **username_5** (2023-07-03): You write *"This means you can't safely store any closable object in a class or struct. Even returning a file from a function safely becomes impossible"*, but removing `with` from the language doesn't make those things safe, nor does it stop you from doing them unsafely.  
> **username_6** (2023-07-04): There's a big difference between "`with` should not be the only way" and "`with` is bad". The vast majority of resource use fits the `with` model, so it's a good feature and has been provided in many languages (I first encountered it in Lisp's `WITH-OPEN-FILE`).  
> **username_3** (2023-07-04): @Barmar Don't you understand my second example? It's nearly impossible to construct custom classes that safely implement the interface. That makes it worse than useless  
> **username_6** (2023-07-04): @mousetail I was mainly addressing "with can only be used if an object is created and destroyed in the same block". That's the whole point of it.  
> **username_6** (2023-07-04): That complex uses are hard to implement correctly doesn't seem like a good objection, unless there's something better. If you try to do "multifile" yourself, you run into the same try/except hell, so why not let one implementor figure it out and package it, rather than force everyone to do it (likely getting it wrong)?  
> **username_7** (2024-12-21): @mousetail'he-him': I don't see any way to really fix the issues you're describing without adding means by which constructors could request notification if an exception is thrown between the instantiation of a new object and the exposure of the object to the code requesting its construction, and adding such means would solve the problems you're blaming on with/using constructs.  That's not to say the problems don't exist, but they're not the fault of with/using.  
---
