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

What is your evaluation of the proposal?

:+1:t3: I'm in favour of this proposal. It will improve the clarity and readability of code that I have in production now.

I understand there are concerns that something more generic should be designed to handle other cases like CGFloat, but I've not yet seen any other similarly widespread type wrappers in use in the ecosystem, so I feel this one is a special case.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes. CGFloat to Double conversions exist in all of my code bases, and it shouldn't be necessary to continually convert between CGFloat-as-a-wrapper-for-Double and Double.

Does this proposal fit well with the feel and direction of Swift?

I feel it does, given Xiaodi's reminder that Swift was envisioned as a pragmatic language. This is a pragmatic change that will make things simpler in use for a great many developers on Apple's platforms using Swift today.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I haven't faced the same issue in other languages that I use.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've read the proposal, and I've been following the threads here on the forums closely as the proposal developed.

4 Likes

-1 from me.

This proposal should be feedback to the CoreGraphics team:
"CGFloat is difficult to work with in Swift, please make it easier -thanks".
1 year later...
WWDC22 - Session 405: Getting started with CoreGraphics2
wow magic :stuck_out_tongue_winking_eye:

Others have suggested broader language improvements that make all imported conditional typealiases better to work with. I don't personally see how that could be done and be safe, but I'd be on board with a proposal like that if someone can figure it out. However this proposal just panders to Apple platforms without actually adding anything to the Swift language. I work with C/C++ libraries all day and have to cast constantly between scalars. That's just the way it is. As I joked above, I think this is a CoreGraphics problem not a Swift problem.

A better proposal that defines an automatic allowed cast between types would be really cool for working with C/C++. Could do a feature that fills the cast in when needed.
Maybe make it declared with imports and file scoped.

import CoreGraphics
autocast CGFloat as Double, Float32, Float16

The type system would choose the cast.
In general this is safety defeating and it would be abused because it's too easy; however it would make some C/C++ stuff much easier to handle.
Perhaps restricted the feature on a package level so package creators are the only ones that can choose to implement it. If the CoreGraphics team decided CGFloat can be implicitly cast to Double they could implement it in their shims. While people like me could make C/C++ packages that expose values of type Int and UInt instead of Int32 and UInt32.

But this proposal as is :-1:

1 Like
  • What is your evaluation of the proposal?

+1. CGFloat is an annoying aberration in my day to day development. Seeing SwiftUI switch to it was especially disappointing. Getting rid of most uses will be great.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes, given that's where the conversion layer lives for these imported APIs.

  • Does this proposal fit well with the feel and direction of Swift?

95%. I'm a big fan of Swift's no-implicit-conversion rule, but I find the proposal's argument that this simply treats CGFloat as an imported API with automatic conversions, as precedented by other types, compelling.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

No.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal and the reviews in thread.

4 Likes

I'm -1 on this proposal as it stands. I want to be happy about the prospect of less boilerplate, but I can't ignore the tradeoffs.


My issue with implicit conversion, is that it makes the case that the two types are interchangeable, but stops short of actually declaring a preference for which one should be used in Swift going forward. Some devs may adopt CGFloat everywhere (documentation will not prevent this, only discourage it), while others will adopt Double.

This will create dialects of Swift, and blind people to potentially lossy conversions in their code. It will do this without actually solving the risk of lossy conversions (the risk increases if anything), or the confusion as to why we have two floating-point types in active use. It purely solves the problem of writing out initialisers in code. I don't think these tradeoffs are worth it for what is effectively ‘syntactic sugar’, and I think we can do better.


The proposal suggests we think of CGFloat as a ‘retroactive typealias’ for Double going forward, but the subtext is that we can all safely forget about the risks of lossy conversions. I suppose this must be taken as given, or else explicit initialisers are clearly the right choice.

Given Double and CGFloat are effectively interchangeable, my preference would be to map CGFloat to Double at the importer level, similar to how NSString is currently mapped to String. Implicit conversions may be a useful tool alongside this change, but simply for backwards-compatibility of source code. This would push users towards using Double universally (rather than interchangeably), eliminating most of the tradeoffs that come with the proposal as it stands.

(The proposal currently lists this as option 4 originally considered to solve this problem, and goes on to note the original reason this wasn't chosen has been shown to be a non-issue!)

3 Likes

It is my take, that we shouldn't worry too much about lossy conversion in the specific case of Double / CGFloat. This inconvenience is mostly affecting iOS and Mac developers who are already doing lossy conversion, just explicitly.

That 0.001% of developers that actually need to worry about lossy conversion will probably be experienced enough to understand that the conversion is happening.

3 Likes

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.

6 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)

1 Like

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.

48 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.

Terms of Service

Privacy Policy

Cookie Policy