In the code that follows, I noticed that the second print outputs Substring. When I plug the same expression as an implicit return in a function that returns a String an implicit conversion seems to take place (refer: someFunctionThatDoesNotFail).
However, when I assign the same expression to an intermediate constant and then return that constant, this implicit conversion does not happen. (refer the commented someFunctionThatFails).
Can anyone explain why?
let string = "some string"
print(type(of: string + string.dropLast())) // String
print(type(of: string.dropLast() + string)) // Substring
func someFunctionThatDoesNotFail(_ string: String) -> String {
// why no error on implicit return?
// is this an implicit cast?
string.dropLast() + string
}
print(type(of: someFunctionThatDoesNotFail(string))) // String
// func someFunctionThatFails(_ string: String) -> String {
// let res = string.dropLast() + string
// // error: cannot convert return expression of type 'String.SubSequence' (aka 'Substring') to return type 'String'
// return res
// }
PS: I hope it's not another one of the "top level" behavior issues that I seem to hit a couple of times.
To elaborate a bit further, Substring conforms to RangeReplaceableCollection, which among other methods contains:
static func + <Other>(lhs: Self, rhs: Other) -> Self where Other : RangeReplaceableCollection, Self.Element == Other.Element
When applied to arguments where Self == Substring and Other == String, this will produce Substring. Since Stringalso conforms to RangeReplaceableCollection, it has the same operator available, and applying it when Self == String and Other == Substring will produce String.
Inside someFunctionThatDoesNotFail, the above overload is not viable, because it would produce Substring rather than String. There is another overload available, which is:
static func + <Other>(lhs: Other, rhs: Self) -> Self where Other : Sequence, Self.Element == Other.Element
We can apply this with Self == String and Other == Substring, producing String as desired, and so this overload is selected.
As for why the original uses of + are ambiguous, Swift has a bunch of rules for overload and type-checker solution ranking to pick the 'best' overload in many situations so that you will end up with something reasonable even if there's not one unique solution that would successfully typecheck.
I looked in my brain, using other parts of my brain. I have some ideas about how I would have gone about this if that had not been possible but they're not efficient or automated. Maybe @Jumhyn knows the right way?
I maybe rephrasing you, but just so I understood, is this what happens?
In the commented someFunctionThatFails function, the assignment let res = string.dropLast() + string does not have information about the return type, hence Swift's "pick the best overload" algorithm can only infer it as a Substring, as implicit conversions aren't a thing in Swift, it fails?