George
1
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...
3 Likes
jrose
(Jordan Rose)
2
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.
3 Likes
jeremyp
(Jeremy Pereira)
3
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
George
4
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
xwu
(Xiaodi Wu)
5
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").
2 Likes
allevato
(Tony Allevato)
6
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.
jrose
(Jordan Rose)
7
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
Nobody1707
(Nobody1707)
9
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