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

Oh that's cute. I'd probably want to say `defer { _ = val }` though, to
make it obvious that this is intentional.

-Kevin Ballard

···

On Wed, Dec 30, 2015, at 01:33 PM, Joe Groff wrote:

Another possibility I've thought of is defining `defer { val }` to
guarantee that val remains alive until the defer fires on scope exit.
That might let us leave `defer` as the one "guarantee something
happens exactly at scope exit" language construct.

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.

If you run with the idea that any resource-holding class should also be the mechanism by which you access the resource (e.g. a LockGuard that represents holding the lock and also provides access to the guarded value) then there's no need for extended lifetimes, because as long as you're accessing the resource, you're keeping the resource-holding class alive. I suppose there might be rare cases where you need to extend the lifetime of a resource-holding class even when you're not accessing the resource, just to guarantee e.g. order of resource releasing, but you can always just say something like `withExtendedLifeetime(&val) {}` at the end of the scope to ensure the value is still alive at that point. Although I'd really like to define `_ = val` as guaranteeing that the value is alive at that point (the expression doesn't actually do anything, but because it references `val` it expresses the programmer's intent that `val` should still be alive at that point in time). Alternatively, if we end up with move-only structs (or uniquely-owned classes), we could even define the expression `_ = val` as "dropping" the value , i.e. forcing it to deinit at that spot (because it's moving the value out of the `val` variable). This would be analogous to Rust's `std::mem::drop()` function (which is literally defined as `pub fn drop<T>(_x: T) { }` because all it does is move the value into the function and then forget about it).

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

+1. This seems like a really good way to do it.

···

Sent from my iPad

On Dec 30, 2015, at 3:33 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 30, 2015, at 1:27 PM, Kevin Ballard <kevin@sb.org> wrote:
On Wed, Dec 30, 2015, at 09:53 AM, Joe Groff wrote:

On Dec 29, 2015, at 8:55 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Honest question, where's the guarantee that the optimizer isn't allowed to
optimize defer {val} away?

···

On Thu, Dec 31, 2015 at 00:33 Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 30, 2015, at 1:27 PM, Kevin Ballard <kevin@sb.org> wrote:

On Wed, Dec 30, 2015, at 09:53 AM, Joe Groff wrote:

On Dec 29, 2015, at 8:55 PM, Kevin Ballard via swift-evolution < > swift-evolution@swift.org> 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.

If you run with the idea that any resource-holding class should also be
the mechanism by which you access the resource (e.g. a LockGuard that
represents holding the lock and also provides access to the guarded value)
then there's no need for extended lifetimes, because as long as you're
accessing the resource, you're keeping the resource-holding class alive. I
suppose there might be rare cases where you need to extend the lifetime of
a resource-holding class even when you're not accessing the resource, just
to guarantee e.g. order of resource releasing, but you can always just say
something like `withExtendedLifeetime(&val) {}` at the end of the scope to
ensure the value is still alive at that point. Although I'd really like to
define `_ = val` as guaranteeing that the value is alive at that point (the
expression doesn't actually do anything, but because it references `val` it
expresses the programmer's intent that `val` should still be alive at that
point in time). Alternatively, if we end up with move-only structs (or
uniquely-owned classes), we could even define the expression `_ = val` as
"dropping" the value , i.e. forcing it to deinit at that spot (because it's
moving the value out of the `val` variable). This would be analogous to
Rust's `std::mem::drop()` function (which is literally defined as `pub fn
drop<T>(_x: T) { }` because all it does is move the value into the function
and then forget about it).

Another possibility I've thought of is defining `defer { val }` to
guarantee that val remains alive until the defer fires on scope exit. That
might let us leave `defer` as the one "guarantee something happens exactly
at scope exit" language construct.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

A uniquely-owned class that guarantees stack allocation is pretty much
the same thing as a move-only value type, isn't it? The only real
difference I can think of is classes allow for subclassing.

-Kevin

···

On Wed, Dec 30, 2015, at 01:18 PM, Chris Lattner wrote:

On Dec 30, 2015, at 10:31 AM, Joe Groff <jgroff@apple.com> wrote:

On Dec 30, 2015, at 10:26 AM, Chris Lattner <clattner@apple.com> >>> wrote:

On Dec 30, 2015, at 9:53 AM, Joe Groff via swift-evolution <swift- >>>> evolution@swift.org> wrote:

On Dec 29, 2015, at 8:55 PM, Kevin Ballard via swift-evolution <swift- >>>>> evolution@swift.org> 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.

It would if we extended the model for value types to be richer, e.g.
to introduce the notion of "move only” structs.

Perhaps, but I feel like it's a more natural extension of our
existing model to support uniquely-owned classes though, which would
give you all the same benefits.

So long as it guarantees no heap allocation for the class
instance, ok.

-Chris

Natural? I have no idea what you're expecting that expression to
actually do. What is a "defer let"?

-Kevin

···

On Wed, Dec 30, 2015, at 03:12 PM, Kevin Wooten via swift-evolution wrote:

Another possibility I've thought of is defining `defer { val }` to
guarantee that val remains alive until the defer fires on scope
exit. That might let us leave `defer` as the one "guarantee
something happens exactly at scope exit" language construct.

What about this…

defer let val = grabOrCreateSomething() { return; }

Seems natural once you learn guard.

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

What about this…

defer let val = grabOrCreateSomething() {
  return;
}

Seems natural once you learn guard.

···

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

What about this…

defer let val = grabOrCreateSomething() {
  return;
}

Seems natural once you learn guard.

Natural? I have no idea what you're expecting that expression to actually do. What is a "defer let”?

I think the idea is that a local variable declared with a `defer` modifier has its lifetime extended until the scope exits. It is a slightly more compact version of what Joe suggested. But I agree that it has potential for confusion - it reads like it is deferring the initialization of `val` until the scope exits which would be rather pointless.

I do like the idea of making the extended lifetime part of the local variable declaration but I’m not sure about how this specific syntax reads.

···

On Dec 30, 2015, at 5:19 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:
On Wed, Dec 30, 2015, at 03:12 PM, Kevin Wooten via swift-evolution wrote:

-Kevin

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Honest question, where's the guarantee that the optimizer isn't allowed to optimize defer {val} away?

There isn't one, today. It would be a language change if we wanted to make that guarantee.

-Joe

···

On Dec 31, 2015, at 2:48 AM, ilya <ilya.nikokoshev@gmail.com> wrote:

On Thu, Dec 31, 2015 at 00:33 Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 30, 2015, at 1:27 PM, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:

On Wed, Dec 30, 2015, at 09:53 AM, Joe Groff wrote:

On Dec 29, 2015, at 8:55 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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.

If you run with the idea that any resource-holding class should also be the mechanism by which you access the resource (e.g. a LockGuard that represents holding the lock and also provides access to the guarded value) then there's no need for extended lifetimes, because as long as you're accessing the resource, you're keeping the resource-holding class alive. I suppose there might be rare cases where you need to extend the lifetime of a resource-holding class even when you're not accessing the resource, just to guarantee e.g. order of resource releasing, but you can always just say something like `withExtendedLifeetime(&val) {}` at the end of the scope to ensure the value is still alive at that point. Although I'd really like to define `_ = val` as guaranteeing that the value is alive at that point (the expression doesn't actually do anything, but because it references `val` it expresses the programmer's intent that `val` should still be alive at that point in time). Alternatively, if we end up with move-only structs (or uniquely-owned classes), we could even define the expression `_ = val` as "dropping" the value , i.e. forcing it to deinit at that spot (because it's moving the value out of the `val` variable). This would be analogous to Rust's `std::mem::drop()` function (which is literally defined as `pub fn drop<T>(_x: T) { }` because all it does is move the value into the function and then forget about it).

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Or you could make it even more obvious what this is by giving that feature a name. For instance:

  defer { _fixLifetime(val) }

The good thing about this one is that it already works, because that's what `withExtendedLifetime` does internally. Also, if you search a bit, you'll end up here with a nice explanation of what that function does.

So why not simply remove the underscore?

···

Le 30 déc. 2015 à 16:40, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :

On Wed, Dec 30, 2015, at 01:33 PM, Joe Groff wrote:

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

Oh that's cute. I'd probably want to say `defer { _ = val }` though, to make it obvious that this is intentional.

--
Michel Fortin
https://michelf.ca

If we need some way to say "this local variable should extend to the end
of the scope", the most obvious (to me) way to do that is to introduce
an attribute that you can add to the variable declaration. This would be
similar to Obj-C's __attribute__((objc_precise_lifetime)), although not
quite as wordy. Although I am tempted to say we should go with `defer {
_ = val }` because I always like when you can re-use existing language
features; that does require we define `_ = val` as extending the
lifetime of val to that point, but as I said before I think that's a
perfectly reasonable thing to do.

-Kevin Ballard

···

On Wed, Dec 30, 2015, at 03:23 PM, Matthew Johnson wrote:

On Dec 30, 2015, at 5:19 PM, Kevin Ballard via swift-evolution <swift- >> evolution@swift.org> wrote:

On Wed, Dec 30, 2015, at 03:12 PM, Kevin Wooten via swift- >> evolution wrote:

Another possibility I've thought of is defining `defer { val }` to
guarantee that val remains alive until the defer fires on scope
exit. That might let us leave `defer` as the one "guarantee
something happens exactly at scope exit" language construct.

What about this…

defer let val = grabOrCreateSomething() { return; }

Seems natural once you learn guard.

Natural? I have no idea what you're expecting that expression to
actually do. What is a "defer let”?

I think the idea is that a local variable declared with a `defer`
modifier has its lifetime extended until the scope exits. It is a
slightly more compact version of what Joe suggested. But I agree that
it has potential for confusion - it reads like it is deferring the
initialization of `val` until the scope exits which would be rather
pointless.

I do like the idea of making the extended lifetime part of the
local variable declaration but I’m not sure about how this specific
syntax reads.

-Kevin

_______________________________________________
swift-evolution mailing list swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

What about this…

defer let val = grabOrCreateSomething() {
  return;
}

Seems natural once you learn guard.

Natural? I have no idea what you're expecting that expression to actually do. What is a "defer let”?

I think the idea is that a local variable declared with a `defer` modifier has its lifetime extended until the scope exits. It is a slightly more compact version of what Joe suggested. But I agree that it has potential for confusion - it reads like it is deferring the initialization of `val` until the scope exits which would be rather pointless.

Given our discussion that’s definitely the gist of it, but I do stand corrected. I guess I meant the syntax felt “familiar” to guard, but I'd agree upon reflection it doesn’t read well.

···

On Dec 30, 2015, at 4:23 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 30, 2015, at 5:19 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Wed, Dec 30, 2015, at 03:12 PM, Kevin Wooten via swift-evolution wrote:

I do like the idea of making the extended lifetime part of the local variable declaration but I’m not sure about how this specific syntax reads.

-Kevin

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

A uniquely-owned class that guarantees stack allocation is pretty much the same thing as a move-only value type, isn't it? The only real difference I can think of is classes allow for subclassing.

At this point, we’re talking about two unspecified and hypothetical models, so of course they’re both equivalent and completely different :-)

We should talk about this in more detail later (perhaps next year, perhaps the year after), but I am pretty concerned with saying that unique ownership of classes replaces move-only types. From a programming model perspective (how the programmer thinks about & designs their code) both capabilities are important. You want move-only struct types in various cases and unique ownership of class instances.

For example, IMO, a uniquely-owned class instance has to be on the heap, because it would have to convert to a multiply owned reference in many cases, and “moving” a class from the stack to the heap is, uh, complicated.

-Chris

···

On Dec 30, 2015, at 1:22 PM, Kevin Ballard <kevin@sb.org> wrote:

-Kevin

On Wed, Dec 30, 2015, at 01:18 PM, Chris Lattner wrote:

On Dec 30, 2015, at 10:31 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Dec 30, 2015, at 10:26 AM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Dec 30, 2015, at 9:53 AM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 29, 2015, at 8:55 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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.

It would if we extended the model for value types to be richer, e.g. to introduce the notion of "move only” structs.

Perhaps, but I feel like it's a more natural extension of our existing model to support uniquely-owned classes though, which would give you all the same benefits.

So long as it guarantees no heap allocation for the class instance, ok.

-Chris

Because it's not a very good API? Having some magic function called `fixLifetime()` that keeps values alive is a little weird. `withExtendedLifetime()` is also slightly weird, but it makes more sense than fixLifetime() does, since withExtendedLifetime() is the specific solution to the problem of "I need to ensure this value remains alive for this entire scope because reasons".

I'd much rather just see the syntax `_ = val` be converted into `Builtin.fixLifetime(val)` because the only reason to write an expression like that is because you want to make sure the value is still alive at that point (that and because it's not unreasonable for people to assume that `_ = val` will keep the value alive, so it would be nice to make that assumption actually be correct).

-Kevin Ballard

···

On Thu, Dec 31, 2015, at 01:40 PM, Michel Fortin wrote:

Le 30 déc. 2015 à 16:40, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :
> On Wed, Dec 30, 2015, at 01:33 PM, Joe Groff wrote:
>> Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.
>
> Oh that's cute. I'd probably want to say `defer { _ = val }` though, to make it obvious that this is intentional.

Or you could make it even more obvious what this is by giving that feature a name. For instance:

  defer { _fixLifetime(val) }

The good thing about this one is that it already works, because that's what `withExtendedLifetime` does internally. Also, if you search a bit, you'll end up here with a nice explanation of what that function does.
https://github.com/apple/swift/blob/8d9ef80304d7b36e13619ea50e6e76f3ec9221ba/stdlib/public/core/LifetimeManager.swift#L45

So why not simply remove the underscore?

I don't think this is obviously true. `val` could be a computed property you just want to force the side effects for. (Computed properties with side effects maybe aren't a great idea, but there are some in AppKit/UIKit.)

-Joe

···

On Dec 31, 2015, at 4:26 PM, Kevin Ballard <kevin@sb.org> wrote:

On Thu, Dec 31, 2015, at 01:40 PM, Michel Fortin wrote:

Le 30 déc. 2015 à 16:40, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :

On Wed, Dec 30, 2015, at 01:33 PM, Joe Groff wrote:

Another possibility I've thought of is defining `defer { val }` to guarantee that val remains alive until the defer fires on scope exit. That might let us leave `defer` as the one "guarantee something happens exactly at scope exit" language construct.

Oh that's cute. I'd probably want to say `defer { _ = val }` though, to make it obvious that this is intentional.

Or you could make it even more obvious what this is by giving that feature a name. For instance:

  defer { _fixLifetime(val) }

The good thing about this one is that it already works, because that's what `withExtendedLifetime` does internally. Also, if you search a bit, you'll end up here with a nice explanation of what that function does.
https://github.com/apple/swift/blob/8d9ef80304d7b36e13619ea50e6e76f3ec9221ba/stdlib/public/core/LifetimeManager.swift#L45

So why not simply remove the underscore?

Because it's not a very good API? Having some magic function called `fixLifetime()` that keeps values alive is a little weird. `withExtendedLifetime()` is also slightly weird, but it makes more sense than fixLifetime() does, since withExtendedLifetime() is the specific solution to the problem of "I need to ensure this value remains alive for this entire scope because reasons".

I'd much rather just see the syntax `_ = val` be converted into `Builtin.fixLifetime(val)` because the only reason to write an expression like that is because you want to make sure the value is still alive at that point (that and because it's not unreasonable for people to assume that `_ = val` will keep the value alive, so it would be nice to make that assumption actually be correct).

I see two possibilities here:

1. If it's a computed property, fix the lifetime of the return value
   anyway. I don't think this is particularly helpful though, although
   all it would do in practice is prevent early-release if the computed
   property is inlined, or
2. If it's a computed property, just don't fix the lifetime. It seems
   fine to me to say `_ = localVar` has the implicit side-effect of
   fixing the lifetime of localVar, and if you say `_ =
   computedProperty` then that's just the pre-existing behavior of
   evaluating the computed property for its side-effects. No need to fix
   the lifetime of the computed property result.

Incidentally, does it makes sense to talk about fixing the lifetime of
stored properties? I assume that only really matters for local
variables, because stored properties are tied to `self`.

-Kevin

···

On Thu, Dec 31, 2015, at 04:46 PM, Joe Groff wrote:

On Dec 31, 2015, at 4:26 PM, Kevin Ballard <kevin@sb.org> wrote:

On Thu, Dec 31, 2015, at 01:40 PM, Michel Fortin wrote:

Le 30 déc. 2015 à 16:40, Kevin Ballard via swift-evolution <swift- >>> evolution@swift.org> a écrit :

On Wed, Dec 30, 2015, at 01:33 PM, Joe Groff wrote:

Another possibility I've thought of is defining `defer { val }` to
guarantee that val remains alive until the defer fires on scope
exit. That might let us leave `defer` as the one "guarantee
something happens exactly at scope exit" language construct.

Oh that's cute. I'd probably want to say `defer { _ = val }`
though, to make it obvious that this is intentional.

Or you could make it even more obvious what this is by giving that
feature a name. For instance:

defer { _fixLifetime(val) }

The good thing about this one is that it already works, because
that's what `withExtendedLifetime` does internally. Also, if you
search a bit, you'll end up here with a nice explanation of what
that function does.
https://github.com/apple/swift/blob/8d9ef80304d7b36e13619ea50e6e76f3ec9221ba/stdlib/public/core/LifetimeManager.swift#L45

So why not simply remove the underscore?

Because it's not a very good API? Having some magic function called
`fixLifetime()` that keeps values alive is a little weird.
`withExtendedLifetime()` is also slightly weird, but it makes more
sense than fixLifetime() does, since withExtendedLifetime() is the
specific solution to the problem of "I need to ensure this value
remains alive for this entire scope because reasons".

I'd much rather just see the syntax `_ = val` be converted into
`Builtin.fixLifetime(val)` because the only reason to write an
expression like that is because you want to make sure the value is
still alive at that point (that and because it's not unreasonable for
people to assume that `_ = val` will keep the value alive, so it
would be nice to make that assumption actually be correct).

I don't think this is obviously true. `val` could be a computed
property you just want to force the side effects for. (Computed
properties with side effects maybe aren't a great idea, but there are
some in AppKit/UIKit.)