SE-0477: Default Value in String Interpolations

Hi all,

The review of SE-0477: Default Value in String Interpolations begins now and runs through April 29, 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 the review manager via the forum messaging feature. When contacting the review manager directly, please keep the proposal link at the top of the message.

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

Thank you,

Xiaodi Wu
Review Manager

31 Likes
  • What is your evaluation of the proposal?

+1, this is a very common pattern that has no particularly good standard and clear way to resolve

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yep, this comes up very frequently for me, though this doesn't solve the issue in other string interpolation contexts (LocalizedStringKey and OSLogMessage). However, given that this proposal is coming from within Apple, I have some hope that those teams will follow suit with back-deployable APIs too :innocent:

  • Does this proposal fit well with the feel and direction of Swift?

Yep, it's a very natural extension that follows from existing syntax. I think it needs to be a bit more discoverable — maybe this would be a case where we should provide a customized fix-it in the compiler to suggest this method instead? EDIT: I noticed the implementation does have this fix-it.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Just a quick read through.

2 Likes

I think there's an opportunity for SourceKit to improve its completions here. I've noticed in Xcode that the completions don't seem to work unless you fully close the interpolation and string literal to get it to parse correctly. In the examples below, • is the cursor position:

var x = "\(•    // no completions available
var x = "\(•"   // no completions available
var x = "\(•)   // no completions available
var x = "\(•)"  // now it shows the `appendInterpolation` overloads that are
                // available

But even the last case is finnicky; sometimes it shows "No completions" at first, but after mashing Option+Esc a couple times or after a brief delay, it shows me suggestions at the top for (_ value:) and (_ value:default:) (the latter which I copied from the proposal into my project locally).

If SourceKit could be more forgiving about the incomplete line here, and if Xcode and other LSP clients could treat entering \( (and equivalent raw versions) as a trigger that would immediately display the completion list, I think that would be a huge improvement for APIs like this one. I'd love to see more formatting-oriented interpolation helpers in the future (my kingdom for an easy way to convert numbers to formatted hexadecimal without importing Foundation), and there's not really a good way to discover any of them once they exist.

Of course, none of this would/should block this specific proposal, which I'm absolutely +1 for. It solves an important problem that anyone who writes Swift runs into very frequently.

9 Likes

+1 this change makes a ton of sense. I've added the \(describing: ...) alternative to my own code, but I like this default: variation.

I'm sad to see that this addition requires changes to the compiler for the fix-its... Are there plans/roadmaps for packages to define their own fix-its so everyone can make a better experience for developers, and not just standard library authors?

1 Like

+1.

A nice quality of life improvement in the standard library.

+1 for me, having default values for String Interpolations is provides a better declaration on whats happening for that specific string. (which we mainly use it like debug print statement).

Some suggestions here:
Is it better to provide a default value for default: parameter (e.g. missing in the proposal mentioned) within the declaration? Personally think this is up to developer's programming habit.

But for me, I don't really want to type "missing" for many times, in case I have multiple lines of String Interpolations.

Thanks for the quality of life improvement!

Huge +1 from me. This is a pain point developers frequently face, and this simple back deployable solution will all but eliminate that pain. I’ve implemented workarounds for this issue several times over the years, so having a solution in the standard library would let me remove all of them.

I also appreciate how this spelling aligns with the default value subscript on Dictionary.

This solves a real pain point! It would be great!

But could it be better?

It's not really a default value as much as a fall-back or alternate. Dictionary uses default because the value is created and used in one operation, and continues to be visible and used thereafter. In this case, the value is consumed immediately by string interpolation and not visible anywhere else.

The default spelling is almost as verbose as the ?? syntax. This is important because breaking up string interpolation to fit is itself a pain. An interpolated string is not like a regular series of expressions that can be broken at any point.

So: would e.g., alt or or be better as the name?

print("\n- title: \(user?.title, or: "")\n\t- name: \(user?.name, or: "")"
2 Likes

I’m a fan.

I’ve suggested or: in the past, but I've ended up liking default: better. The way it matches the dictionary subscript is nice, and we don’t need to worry that people might confuse or: with the || operator.

7 Likes

I think default is better than both or and alt.

The word or does not convey the sense of being a backup option, and could be understood as a co-equal possibility. The word alt does imply a backup option, but I don’t think it reads well.

If for some reason default were not going to be used, then I might suggest else:

let age: Int? = nil
print("Your age: \(age, else: "missing")")

But I think default is the way to go.

5 Likes

One weakness of this proposal is that the familiar “nil” in ObjC's “%@” is not an implicit default.

Although not a strong opinion, the following API could be considered.

extension String.StringInterpolation {
    mutating func appendInterpolation<T>(
        opt value: T?,
        or alternative: String = String(describing: Optional<T>.none)
    ) {
        if let value {
            appendInterpolation(value)
        } else {
            appendLiteral(alternative)
        }
    }
}

struct FooError: Error {}
let error1: Error? = FooError()
let error2: Error? = nil
print("Error 1a: \(opt: error1)")
print("Error 2a: \(opt: error2)")
print("Error 2b: \(opt: error2, or: "none")")
print("Error 2c: \(error2, default: "none")")
// Error 1a: FooError()
// Error 2a: nil
// Error 2b: none
// Error 2c: none

I believe that starting with opt justifies the use of the short or.

However, I also agree that names like \(opt:or) are too simple for a standard library.

Perhaps the more serious problem is that it is hard to realize that String.StringInterpolation is extensible.
Is it mentioned in the Language Guide or elsewhere?

I’ve seen (null) enough times from Objective-C code that IMO any fallback value should be explicitly specified.

6 Likes

+1 for this proposal.

I think it's a small addition for improved ergonomics of a very common pattern.

Requiring the value to be explicitly specified makes the default value clear, as opposed to an unexpected automatic default value appearing in the output. (Which is what the existing warnings are trying to prevent with "nil".)

I believe default: is a good name for the parameter, for the reasons stated in the proposal.

Of all the proposed alternatives in the thread, else: would be a distant second choice.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

The Liquid template language uses default to indicate a value to be used when a variable is nil. I realize Swift is not a templating language, but having used Liquid for a number of years, I have never thought of this as a bad or confusing name.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read through the proposal a couple of times, played with the swiftfiddle, and read the responses in the thread.

One fun thing: since the default value is a String, you can use string interpolation for the default value, which can have its own default value.

3 Likes

I like this proposal. It's a very common problem and this would be a good implementation. I also like the "default" label.

+1 from me, I have been through the necessary shenanigans to get nil non-String values to show up in interpolations many times… (and I like “default” as a way of specifying the missing value owing to similarity to Dictionary)

I assume that the fix-it will be changed to mention the default value? This is hinted at in the comment thread but I didn’t see anything explicit in the proposal .

1 Like

Worth mentioning, I think we've got some toolchains where you can try out the feature:

3 Likes

What about supporting default: @autoclosure() -> Substring, in addition to ... String?

Substring is not really needed for static dummy defaults acting as placeholders, but it helps for compositions and when Substring is the currency type.

In some code contexts Substring is the currency type, to avoid needless copies. For me it's been very effective -- well-suited for situations like string interpolation where the value is transient, esp. when surfacing parsed values lazily in composed output.

Here the Substring parameter would also avoid the possible recursion of interpolated default values (not sure if that opens any optimization opportunities).

Are there reasons not to support an overload for Substring?

(Or would an overload for any StringProtocol would be helpful or preferable, mainly for generality? I hesitate at the runtime cost of any. Conversely, would StaticString be a helpful overload?)

2 Likes

If you want to go that route, couldn't you make the default some StringProtocol?

4 Likes

Love that this has finally surfaced – been a pain as long as I can remember. One thing I would say:

The beauty of string interpolation is simplicity and readability. Once you start introducing additional parameters, it goes away.

I've been using a custom operator to keep it as minimal as possible:

infix operator ???: NilCoalescingPrecedence
func ??? <U>(optional: U?, default: @autoclosure () -> String) -> String {
    switch optional {
        case .some(let value): return "\(value)"
        case .none: return `default`()
    }
}


"Foo \(bar ??? "missing")" // A little better, but not perfect.
"Foo \(bar, default: "missing")" // On the other hand, like the explicit clarity.

// But for more complex stuff, it starts to look clumsy:
"App v\(version ??? "MISSING") with \(distribution ??? "UNKNOWN") distribution at location: \(location ??? "UNKNOWN")"
"App v\(version, default: "MISSING") with \(distribution, default: "UNKNOWN") distribution at location: \(location, default: "UNKNOWN")"

In that regard, formatted strings feel more readable:

String(
  format: "App v%@ with %@ distribution at location: %@", 
  version ??? "MISSING",
  distribution ??? "UNKNOWN",
  location ??? "UNKNOWN"
)
String(
  format: "App v%@ with %@ distribution at location: %@", 
  "\(version, default: "MISSING")",
  "\(distribution, default: "UNKNOWN")",
  "\(location, default: "UNKNOWN")"
)

So, with readability in mind, the question I'm asking myself is if there's a better approach to address the readability? Can this be focused more on the Optional → String rather than specifically optional interpolation?

Is it worth considering overloading the standard ?? operator so it would return a String? I think this is the most compact and natural way everyone does it "Foo \(bar ?? "missing")", before they realize bar is not a String?

Something like this, basically:

func ?? (optional: String?, default: @autoclosure () -> String) -> String {
    optional ?? `default`()
}

func ?? <U>(optional: U?, default: @autoclosure () -> String) -> String {
    switch optional {
        case .some(let value): return "\(value)"
        case .none: return `default`()
    }
}
3 Likes

The macOS toolchain is now available here: https://ci.swift.org/job/swift-PR-toolchain-macos/1911/artifact/branch-main/swift-PR-80547-1911-osx.tar.gz