String literal initializer preferred over standard initializer

I just noticed this unusual behavior:

struct Foo {
  init(_ string: String) {
    value = "String"
  let value: String
extension Foo: ExpressibleByStringLiteral {
  init(stringLiteral: String) {
    value = "StringLiteral"
print(Foo("This is a string").value)
/// Prints "StringLiteral"

I would have expected "String" to be printed, and "StringLiteral" to be printed if I did print("This is a string literal" as Foo) or wrote a string literal in place of a Foo argument. What is the rationale behind preferring the literal initializer?

Is there any way to influence this behavior? I tried @_disfavoredOverload and that didn't help...


This is SE-0213, a change made to fix the costs of converting twice. I don’t think there’s any way to influence this behavior at the definition site, but I wouldn’t actually recommend it anyway, because you’re going to confuse people if you do.


Oh dear, that's a disaster. Unless you are aware of the existence of SE-213, this is going to look exactly like a horrible bug.

Which, in my opinion it is.

1 Like

I see, thanks for the link!

It’s a bit unfortunate for me, my use case is that when I initialize my type from a compile-time literal I can circumvent some checks which I don’t want to skip of initializing from an arbitrary string.

Also related, I’ve been exclusively using ‘as Foo’ to coerce literals into the correct type because this behavior is surprising to me.

1 Like

You are indeed using the intended spelling. However, experience showed that users pervasively expect Foo("asdf") to mean "asdf" as Foo, so SE-0213 made it guaranteed that it does. Even in the absence of SE-0213, though, it would be very unwise to create a type that deliberately breaks this user expectation.

That said, if you specifically want the init(_:) overload, you can spell it out explicitly: Foo.init("This is a string").


Unless I'm misunderstanding, doesn't the post-SE-0213 still let you achieve that? "abc" as Foo and Foo("abc") both call the stringLiteral initializer because they're both being initialized from a compile-time literal, so you should be able to treat them the same. If someone writes var s = "abc"; _ = Foo(s), then that would call your _: String initializer instead.

Right, there are several things you can do at the call site (as String being another one). But I don’t think there’s a way to change the behavior on the declaration side.

1 Like

over time i’ve started recommending dispensing with the ExpressibleByStringLiteral conformance entirely unless the type really is just a wrapper around an arbitrary string.

one thing i’d be interested in is if there were a way to hook a macro up to a literal expression without the heavyweight expression macro syntax.

1 Like

Compile time code evaluation really is the correct way to do user defined literals, but we're a long way from getting a non-macro form of that. consteval constructors are sorely missed.

1 Like