SE-0307: Allow interchangeable use of CGFloat and Double types

I would make the case that the dialect issue you describe is already present, since developers can already choose to work in Double and convert to CGFloat only when passing the value into API that requires it, or work entirely in CGFloat, possibly converting Double values from APIs that return them. Or, they can choose a third dialect, where a developer tries to minimize the number of conversions, so as to have the least cluttered code, in which case they might switch between the two styles in the same code base depending on the calculations involved.

I believe since this proposal makes it easiest to use Swift's default floating point type Double that the proposed change will likely make doing intermediate calculations in Double much more common.

Of course, developers could choose to work in one type or the other just as they can today, but improving the ergonomics of working with Double is likely to consolidate coding style around Double, especially since it matches the typical style for working with floating point types elsewhere in Swift.

I would say that having Double be the default type used in Swift to interpret floating point literals does declare a strong preference that Double is the preferred floating point type, as well as that being stated in the documentation.

On platforms with a 32-bit CGFloat, frameworks that rely on CGFloat almost always also use the types CGPoint, CGSize, and CGRect. These types allow developers to initialize using Double values, and then silently narrow them to 32-bit CGFloat members. Those initializers have been in place for at least five years, without causing widespread issues. So, in practice there does not seem to be a practical safety issue with silent conversion to 32-bit CGFloat.

There is even less reason to worry. Since iOS and macOS are now exclusively 64-bit platforms, CGFloat and Double have identical 64-bit representations, so there is no loss moving between them.

9 Likes

Thanks for the responses! All good points.

I must admit that in the course of following this discussion (and even writing out my original comment) I'm coming round to this proposal, at least in absence of any practical solution to eliminate CGFloat entirely.

I'd hope that the importer changes would be possible at some stage, but applying them to properties, for types like CGSize, would admittedly be unprecedented.

1 Like

+1 I’d rather see this with the idea it could eventually be deprecated then rushing to implement an implicit conversion feature to address a single problem that probably won’t exist by the time we reach SwiftUI v10.0

2 Likes

-1 casting behavior in swift is already too complex for me, and this would add even more complexity.

for example the document explaining the behavior of dynamic casting (as? as! is) is 745 lines or 36.8 KB
and it doesn't even include description of implicit casts, or the as operator. This proposal would make it even more complicated, by adding even more exceptions to the rules.

I'm not the only person that feels like the current situation is too complex


To people in this thread that talk about the goal of eliminating CGFloat:

The idea that CGFloat would be eliminated is silly. CGFloat would remain here forever.

Take for example these two obj-c methods:

    [[NSString alloc] initWithString:foo];
    [NSString stringWithString:foo];

When ARC was introduced (a decade ago!) both of these method suddenly are completely interchangeable, just like CGFloat and Double will be after this proposal is accepted. Both of them are still here. Both of them are still supported. Neither is deprecated.

Today people want to eliminate CGFloat because the conversions bother everyone. After this proposal, nobody would care, and use them almost randomly.

5 Likes

It seems like you are stating both that this proposal will cause too much additional complexity, but also that if this proposal is adopted, nobody will care that there is an automatic conversion between CGFloat and Double.

My guess is that in this case the latter is the likely result. People will treat CGFloat and Double similar to a typealias like Double and TimeInterval and won't really need to think about it all that much.

I definitely agree with you that the bridging of Objective-C class types to Swift types is fairly complex to understand in detail. But even here, I think most Swift developers are using Objective-C frameworks successfully from Swift without needing to understand all of the details. I think it's also the case that bridging things like NSString to String, where a reference type becomes a value type, also has a lot more inherent complexity than, for example on 64-bit CGFloat platforms, treating one 64-bit float representation like an identical 64-bit float representation.

With a framework API, it's important not to break backwards binary compatibility. Removing a symbol would break that backwards compatibility, so neither method can be removed altogether because existing apps rely on both symbols. Also, manual retain release is still supported in Objective-C, although used far less frequently, so the symbols still serve the purpose of their original use case.

But note that when the NSString API was brought to Swift, which always uses ARC and which initially did not have to consider binary compatibility, only one variant was brought forward to be available in Swift: init(string:).

Because of the need to not break already shipping apps, changes like this do take a long time. But I would make the case that the issue you bring up is addressed in Swift's API for NSString. Similarly, this proposal enables a move away from CGFloat but on a timeline that plays out over years—but it makes that possibility available.

3 Likes

They could call it SwiftDraw :wink:

But I’m also -1 for basically the same reasons as @STREGAsGate . I think making CGFloat easier to deal with helps propagate the underlying problem (that CGFloat shouldn’t actually exist) and has a few other minor downsides also pointed out up-thread.

FWIW I find CGFloat really annoying.

5 Likes

When I write a function, I need to make sure it's correct. That is the step that would be harder with this proposal. Current casting rules are so complex, and undocumented, that sometimes I have to run experiments just to figure out what will happen. The worst thing is when my experiments are lying to me

Example of conversion rules being so complicated, that even the official debugger gets it wrong
func f<T>(_ foo: T) {
    let test = foo != nil
    print(test)
}
let x: Int? = nil
f(x)

Compiler automatically convertsx into a double optional, and prints true
Debugger keeps it as a single optional, and prints false

After I write this function, and let's say it's called 100 times, I don't care if it's being called 99 times with Double, and 1 time with CGFloat, or maybe it's called 50 times with Double and 50 times with CGFloat. I had to prepare for both, so now I don't care about that.

I hope this clears up why I care about automatic casting being present, while simultaneously not caring if it happens sometimes, or a lot.

I agree completely with this paragraph, most people are using casting successfuly without needing to understand all of details, and that this addition isn't as complex as whatever we have already. But am I weird by wanting to know what the code I am writing is doing?

Wouldn't it make harder to move away from CGFloat, if it starts to be used more? (because people treat it just like a typealias)

No, because it allows new API to use Double and still interoperate with existing API using CGFloat. The current state of things is that we would like new libraries to use Double, but they are de-facto forced to use CGFloat to avoid gratuitous conversions when they interoperate with existing API.

7 Likes

Proposal Accepted

After careful consideration of the thoughtful discussion during the review and pitch threads, the Core Team has decided to accept SE-0307.

The Core Team recognizes this was a divisive topic.

While the consensus of the review discussion recognized the friction caused by the duality of the CGFloat and Double types, there was healthy disagreement on the thread on what principles to apply to addressing this problem. Every bit of complexity we add to Swift has an implementation cost to the compiler and a cognitive cost to the user, and implicit conversions — even narrowly defined ones — are a form of magic in the language that can solve problems add nontrivial weight. Thus the Core Team took the feedback on the review thread very seriously, weighed the concerns, and tried to make a balanced decision based on the proposal's goals and the long-term implications.

A large amount of Swift's design, from the earliest days, has been influenced by a strategy of cohesive and effective platform integration. Swift's interoperability with Objective-C and C remains a keystone in its success as a language. That enabled developers to use Swift to meaningfully build software first for Apple platforms and later elsewhere while leveraging the existing APIs on those platforms. Unsurprisingly, the C and Objective-C "bridge" in the compiler (aka, the "ClangImporter") that translates C and Objective-C APIs into Swift APIs is full of both principled translation rules and heuristics tailored to the API patterns of Objective-C and specific types in the Apple SDK.

The Core Team believes the narrowly defined implicit type conversion, as described in SE-0307, addresses a long-standing impedance between Swift code and a platform type — CGFloat — whose inception long predates Swift. The Core Team feels that implicit conversions, as proposed in SE-0307, can be wielded sparingly to solve narrowly focused problems that arise from platform compatibility and interoperability. In this case, such a solution can be justified when the developer's cognitive load is a net positive by removing a significant point of friction when writing code for frequently used APIs. In this case, we can understand the implications of the implicit conversion on the compiler's performance. Finally, it solves an apparent standing problem for which alternative solutions cannot sufficiently address.

However, the Core Team still believes that generalizing support for implicit conversions would be harmful to the language. Implicit conversions, in general, would make code difficult to reason about and compromise the compiler's reliability and performance to infer types.

Thank you, everyone, who participated in the review.

47 Likes

We don't seem to have an accompanied #evolution:announce thread for this one.

Hello Ted and the core team, I as a daily Swift user would wish that the language would explicitly make implicit bridging conversions visible. The Apple frameworks (even Foundation) seem to lack this feature and it‘s very hard to discover if you missed or simply didn‘t follow the language evolution. I for myself spent a few hours recently researching how to use UIFont in SwiftUI. I didn‘t know that I could just implicitly bridge UIFont to CTFont. That said I would appreciate if type bridging and implicit conversions were somehow discoverable like type inheritance or protocol conformances.

12 Likes

Sorry, I forgot to do that. I will add it now.

1 Like

It’s a good suggestion. I’ll look into it, but if folks are interested they can spin up conversations here on the forums to talk about how such a think could be implemented from he compiler’s side. Ultimately any feature built into an editor would need the compiler’s knowledge to power such an experience. For example, a SourceKit API to provides this information. Etc.

8 Likes

Awesome!! Thanks for making Swift and the APIs easier to use.