SE-0499: Support ~Copyable, ~Escapable in simple standard library protocols

Hello, Swift community!

The review of SE-0499: Support ~Copyable, ~Escapable in simple standard library protocols begins now and runs through December 2, 2025.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by email or DM. When contacting the review manager directly, please put "SE-0499" in the subject line.

Trying it out

If you'd like to try this proposal out, you can download a toolchain supporting it for Linux, Windows, or macOS.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at:

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Thank you,

Holly Borla
Review Manager

21 Likes

Since I raised the question in the pitch thread about the suitability of LosslessStringConvertible for ~Copyable types given that it provides a way to copy them, I wanted to make sure I got on the record here:

I've come around to the thinking that there do exist types where the combination of ~Copyable and LosslessStringConvertible makes sense.

While the primary meaning of ~Copyable on a concrete type is that it's not copyable (through Swift's standard means w.r.t. value types), that suppressed conformance has other impacts on data type design. Most notably, ~Copyable value types can declare deinitializers. A ~Copyable value type has the ability to do its own raw memory management without having to introduce an additional level of indirection (e.g., a nested class) in order to implement the necessary tear-down logic. On some platforms, the difference between one allocation vs. two allocations per value can be significant.

With that in mind, an example that came up in an offline conversation was an arbitrary precision numeric type. Such a type may want to be ~Copyable to do said memory management, but it's also perfectly reasonable for that type to want to offer lossless conversion to/from String. So I withdrawn my original concerns about LosslessStringConvertible being present here.

9 Likes

I thank the author(s) very much for this! I've had to compromise multiple times working with noncopyable types where correctness and performance matter and this change will allow me to structure data the way I want. I look forward to ~Copyable and ~Escapable being supported more through the standard library.

I think perhaps another way to look at this would be that there are use cases for ~Copyable types where identity isn't really an essential part of the semantics, such that two not just equal but identical values can simultaneously exist one initialized from another (just not via implicit copies).

2 Likes

Additionally, Optional and Result will have their Equatable and Hashable conformances updated to support ~Copyable and ~Escapable elements.

One of those conformances isn't in the proposal or implementation:

extension Result: Hashable where Success: Hashable & ~Copyable & ~Escapable, Failure: Hashable {}
1 Like

Thanks for catching that, I'll add it.

3 Likes

This is a natural extension to the stdlib. I'm strongly in favour.

What is the expected behaviour of String.init(describing:) in a generic context? Today, the argument can be boxed in an existential and the initializer will unbox it; that isn't possible if the caller adds ~Copyable to its generic constraints list, which ultimately limits the utility of CustomStringConvertible (et al.).

You'll note the documentation for that initializer advises against reading var description directly. So at a minimum, you probably need the proposal to add one or more overloads of String.init.

3 Likes

+1, very much needed.

In general, the printing/stringifying infrastructure needs more work to fully adapt to noncopyable types, since it's currently based around existentials which don't yet fully support noncopyable types. This is beyond the scope of this proposal.

The admonition to not use .description directly is about preferring string interpolation, print etc. instead. It wouldn't really make sense to just add some more String initializers purely for the purpose of not calling .description and instead calling a dedicated String.init that just turns around and just calls .description.

There's an overload of the init(describing:) initializer that takes a T: CustomStringConvertible, so I think we could at a minimum update it via the usual mechanisms to take T: CustomStringConvertible & ~Copyable & ~Escapable instead, no?

The documentation for this member would be outright incorrect if/when this proposal lands:

Calling this property directly is discouraged. Instead, convert an instance of any type to a string by using the String(describing:) initializer. This initializer works with any type, and uses the custom description property for types that conform to CustomStringConvertible:

I'd respectfully suggest that, as part of your work, we update this documentation to describe when it is or is not okay to directly use the description property. (Doubly so if you can't use String(describing:) with some value!)

1 Like

So there is, fair enough. I can add that.

Well, it's already outright incorrect because "This initializer works with any type" isn't true. Which is a problem – but I'm a little wary of doing an interim tweak to draw crenelations about partial fixes to this that e.g. work on concrete types but not arbitrary T in a generic context.

1 Like