(myChar is of type Character, not eg Optional<Character>.)
Which .init is being called there?
Alt-clicking the init in Xcode suggests that it is init?(_ description: String) but that can't be right since the following shows that it isn't a failable initializer:
Jumping to the definition of the intit will take me to this (declared in an extension to Character):
/// Creates a character from a single-character string.
///
/// The following example creates a new character from the uppercase version
/// of a string that only holds one character.
///
/// let a = "a"
/// let capitalA = Character(a.uppercased())
///
/// - Parameter s: The single-character string to convert to a `Character`
/// instance. `s` must contain exactly one extended grapheme cluster.
@inlinable public init(_ s: String)
which I guess might be true. But the documentation of this initializer says that the given string must be a single-character string that contains exactly one extended grapheme cluster (which the string of my example doesn't), and it says nothing about what should happen if the given string doesn't meet those requirements. Shouldn't it trap or be a failable initializer?
Is the Character myChar really a "single extended grapheme cluster that approximates a user-perceived character"?
And if so, what does that user-perceived character look like? The following does not work (as expected):
I understand that to be a rhetorical question, but just in case it isnβt: That string contains 5 extended grapheme clusters (one space, three letters and one Emoji cluster); it is not a valid Character.
No. In addition, when I pasted it into a playground (Xcode 10.3 + Swift 5.0.1), it trapped as expected:
Fatal error: Canβt form a Character from a String containing more than one extended grapheme cluster
Are you seeing this happen in a newer or an older version of Swift?
And if I start a new Command Line Tool (Xcode 10.3 (10G8)), and let the main.swift file be the same code as above, it will also trap as expected.
But in the Xcode project in which I bumped into and this issue, it sill behaves as reported in the OP ... Trying to isolate the issue, I can remove all code in the project except the main.swift file containing this:
I don't have Xcode 11 beta installed, and I guess I thought it might be interesting to know if anyone else can reproduce it using that project, both in Xcode 10.3 and 11 beta (with Swift 5.1), or if it's just me.
I'm not sure why you think it shouldn't be investigated on 5.0.1 (and 5.1)?
With the reason behind the difference in behaviour now tracked down, I suppose we can say that this isn't a bug. The documentation says:
s: The single-character string to convert to a Character instance. s must contain exactly one extended grapheme cluster.
so it is clearly a programmer error. Whether it makes sense to enforce this at runtime in optimised builds will depend on the performance impact. On the other hand, I'm a little surprised that this isn't picked up at compile time even in optimised builds for this particular case, but I know that Unicode evolution means that this kind of validation can only be done in a best-effort manner.
I still can't see any good reasons why Character.init(_ s: String) shouldn't trap (even in release builds) if s cannot be represented as a valid character (ie exactly one extended grapheme cluster).
The number of extended grapheme clusters in any String is always available at runtime via eg s.count, and if s.count != 1 it could simply trap, no matter if it's debug or release, couldn't it?
If it is for performance reasons, I think adding a Character(unchecked s: String) is better a better option than leaving the existing one as is (ie silently creating strange non-character Character values).
I have had somewhat hard to find bugs that would have been obvious if this init had trapped.
Sure, performance reasons are exactly why _debugPrecondition exists. I doubt converting every function in the standard library that uses _debugPrecondition into separate checked and unchecked versions is the right answer, but perhaps it makes sense in this case. I guess someone could look into the performance impact here. Note that the unchecked initialiser already exists, but it is currently internal.