It still allows you to use them for interop. The only functionality that would be impaired is checking for inheritance with an `as?` type check/conform.
It impairs that principle, yes. But it doesn’t block similar patterns that give you similar end results. Swift has very flexible generics programming and type abstraction that still lets you use it with a little extra work.
Yes, it won’t be as ergonomic as the ideal, but it’s better than many binding solutions from C++ to compiled languages, while providing most of the usability.
Plus the footnote says that they plan to resolve this in future Swift versions.
C++ interop is impressive, and notoriously hard to get right. This seems like it would be able to cover a lot of APIs.
Swift and C# are the well-designed languages I'd rather use at my work instead of Go. It's a real shame both are effectively locked into a specific platform.
You can really tell that they had years of attention and careful design by experts in PL poured into them. Go seems like an amateur hobby project in comparison.
Currently developing both in swift and go and i have the exact inverse feeling.
Go seems to have been developped by true masters that really understand that less is more. They've also kept a laser sharp focus on working on things that really matters and took the time to do things right.
Swift on the other hand looks like more and more bloated every day, without adressing the main pain points of the experience of coding in swift in the real world.
But developer experience is a factor that should weigh heavily in evaluating the quality of a programming language.
I also fear Swift is going a step too far. For example, IMO Swift's compilation speed isn’t good enough.
If that’s a matter of lack of tooling, I won’t blame the language, but is it, or is its slowness unavoidable for a language that is so flexible? If the latter, I would rather have a bit less flexibility and more compilation speed.
Similarly, there’s quality of error messages. Swift has gotten better there, but still has some weird ones. Are they unavoidable for the current language?
When discussing compiler speed, Go specifically prioritizes compilation speed over optimization of the resulting code.
Swift and Rust both suffer from slower compilation than Go, primarily because LLVM prioritizes optimization over speed.
There are of course other reasons like complexity of language, size of compilation units etc… but optimization tends to be the biggest hit in my experience.
Hence why Rust is adopting/investigating cranelift as a debug compiler to improve speed.
> There are of course other reasons like complexity of language, size of compilation units etc… but optimization tends to be the biggest hit in my experience.
I guess you don’t have much experience with Swift. LLVM code generation may be slower than alternatives, but that’s more or less by a constant factor.
Swift’s type inference, on the other hand, can lead to a combinatorial explosion.
let sum = [1, 2, 3].map {
String($0)
}.flatMap {
Int($0)
}.reduce(0, +)
takes over 11 seconds to compile, while
compare this code:
let sum = [1, 2, 3].map { (num: Int) -> String in
String(num)
}.flatMap { (str: String) -> Int? in
Int(str)
}.reduce(0, +)
takes about 75ms, and
let numbers = [1, 2, 3]
let stringNumbers = numbers.map {
String($0)
}
let intNumbers = stringNumbers.flatMap {
Int($0)
}
let sum = intNumbers.reduce(0, +)
compiles in 71ms.
Of course, all of these use LLVM, and likely even generate highly similar intermediate code.
Also, you’ll see these slowdowns even in debug builds that don’t ask LLVM to optimize code much.
Pretty rude to start your comment with the assumption that I’m not experienced with Swift. The rest of your comment would be more useful without the unnecessary condescension.
But nothing you said contradicts what I said either. Yes it’s easy to get into slow paths with Swift, but it’s also easy not to. Granted it takes a lot of up front knowledge to do so.
But even if you just stick to the LLVM portion, llvms debug mode is slower than cranelift and other toolchains debug modes. Debug doesn’t mean “no optimizations” whatsoever, nor does it mean efficient paths through the toolchain either.
I would be fine with slow, if the Swift compiler could actually compile all valid Swift programs. It’s pretty commonplace for me to have to break a large or complicated function into smaller bite-size pieces because the compiler can’t type check it all at once.
I had several years in the ObjC ecosystem, embraced Swift whole-heartedly immediately at 1.0, and was surprised by people nitpicking like this.
It's been humbling to come back to it over the intervening 7 years and realize they weren't nitpicks that would be resolved shortly, and people would understand if they just read the swift-evolution mailing list. They were fundamental flaws.
If they removed the few small features that are responsible for 80% of the slowness and called it optimized swift I bet many would embrace it fairly quickly, especially considering they are functionally cosmetic features.
Not type inference, rather, whatever is so pathological about the fundamental algorithm for doing Swift type inference that it can become an infinite loop.
This is a Swift-only feature in major languages with type inference.
> Primary-file mode's advantages are that the driver can do incremental compilation by only running frontends for files that it thinks are out of date, as well as running multiple frontend jobs in parallel, making use of multiple cores. Its disadvantage is that each frontend job has to read all the source files in the module before focusing on its primary-files of interest, which means that a portion of the frontend job's work is being done quadratically in the number of jobs. Usually this portion is relatively small and fast, but because it's quadratic, it can easily go wrong. The addition of batch mode was specifically to eliminate this quadratic increase in early work.
One example that would improve the above: replace module imports with file specific imports, including within the module itself. You could also make a case for operator overloading and there are probably a bunch of candidates a swift compiler engineer could tell you that would be small but significant like that.
This used to irritate me, but now that I'm in the habit of breaking functions up I've come to prefer it. It makes for code that's easier for myself to read and follow further down the road, which I think is a reasonable proxy for readability by others.
Writing modular code is good but that's a ridiculous reason to need to do so.
Xcode also trips on simple type errors and sometimes fails to even provide a line number where the error lies. As someone who just wrote a Swift app after no prior exposure, it's oddly immature in some basic ways after nearly a decade from launch.
Yeah I won’t defend it too much, Xcode/SourceKit/etc are clearly written in a way that assumes devs are writing perfectly idiomatic, smell-free code which isn’t very aligned with reality. I’ve more or less got a feel for what makes it grumpy so I don’t trip it up too often any more but it’s annoying on the odd occasion I do.
If you are writing functions that the current/latest Swift compiler can’t handle on modern hardware then I feel sorry for anyone who has to someday read your code.
It's a subjective issue, but sometimes the end results can be better even if the experience of getting there is not as nice as a simpler language that is more bug-prone. Consider Rust, which is notoriously difficult to learn, but that offers improved end results in many cases re: security, safety, reliability.
Can you explain how that's not an aspect of developer experience? It seems to be like trading off between type system correctness (you can go all the way to needing full formal proofs of correctness, resource use, etc) and ease of getting stuff done is the core of developer experience.
Does your idea of a good developer experience include programs that are reliable, won't crash in production, won't be compromised, and save you lots of time later on so you can just focus on writing code the first time? In the absence of these guarantees, your developer experience can include some very frustrating and/or stressful debugging.
Tools like Agda, Spark, Idris, and Coq move the tradeoff around towards "probably correct", and I welcome seeing some of their features become gradually available. Getting things right the first time is useful.
But these features can also make it hard to get anything done quickly.
Good developer experience means finding a balance between the quick and dirty, and the provably correct.
I think Go is made for writing a certain kind of software that is important for Google, and for a certain kind of developer Google wants to have writing this software. That the result is not the kind of language I would want to use is immaterial. I'm quite sure Go is masterfully targeted for the purposes Google wants it for.
1. Borg precedes K8s and likely is tightly coupled with Google's backend infra - that's to say, Borg gets architected around Google's existing workflow and new backend development is written around Borg's workflow.
2. GoLang was never intended to be an OS-level programming language. It was created to enable more robust, efficient, and rapid development in a particular space. It would be just as silly to argue that Google's three OS projects all eschew Dart.
I'm not actually criticizing GoLang, just pointing out some facts.
1. is fair.
2. I'd argue is false, given Fuschia and Dart are tightly integrated (as another commenter noted) and that GoLang was originally designed as a C++ replacement born from the Plan9/Inferno tool chains (certainly "OS-level"). Also there's a ton of non-kernel code in an OS so this is a really blurry distinction. containerd does some pretty low level stuff with Linux and GoLang is perfectly capable of this.. if Fuschia wanted to use it they could.
Go was created by three folks that got fed up waiting on C++ compile times, and from their point of view Go is designed for people unable to take feature rich languages, on Rob Pike's own words.
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt."
Or alternatively,
"It must be familiar, roughly C-like. Programmers working at Google are early in their careers and are most familiar with procedural languages, particularly from the C family. The need to get programmers productive quickly in a new language means that the language cannot be too radical."
indeed that would be number 1 by a VERY high margin. It's pretty ridiculous to start with "world domination" as an objective and end up 10 years later without a portable stdlib (aka foundation).
Another issue would be a coherent concurrency system. Today we have a mix of gcd and actor system, declarative task, async await, etc which when put together makes me totally unable to understand what's going on in a real world program, unless you're doing like i do in my team : i specifically ban all modern concurrency apis in favor of the old ones.
The same could be said with protocol extension + generics + class + structs.
For each problem, there are approximately 4 possible designs with subtle tradeoffs. Swift team advocates for "expressiveness". My personnal experience is that all patterns and technics will ultimately end up showing in the codebase, depending on the mood of the developer.
In the end, i've started to do like C++ developers end up doing : write guidelines on which language features are allowed, and which are forbidden.
Compare that to go, where there's (at most) one satisfying way to design the problem in the typesystem. As a sideeffect people have to think more about the problem they're trying to solve, and how to simplify it to the maximum.
The end result is that go pushes you toward simple and maintainable code. Swift pushes you toward being fancy for no good reasons.
you are referring to basic languauge features... you consider classes and structs pain points? these are the basic building blocks of the language, if you find them painful this is definitely not the language for you.
protocol extension adds another axis of design, with all its new gotcha ( pat , overrides, etc)
It looks great on paper and do enable beautiful designs in some cases. But after tasting languages like go you realize you can get away with struct & functions in 99% of the cases, and that having less design options is actually a plus. It lets you focus on what really matters : domain modeling.
It's a lot more than "looks great on paper." My suspicion is that you have not really used and benefited from these features in a non-trivial way or you'd understand why they are there.
I wouldn't want to be the guy relying on this table advancement for his own project. The fact that they're rewriting it in pure swift probably says a lot about the quality of the current approach.
b: makes absolutely no difference from a developer perspective. if you want to run threads in swift you're going to use gcd.
c: my take with all apple software tech has been to wait until they've dogfooded their own tech long enough to make it useable. Worked very well for me so far, thank you very much.
> Another issue would be a coherent concurrency system. Today we have a mix of gcd and actor system, declarative task, async await, etc which when put together makes me totally unable to understand what's going on in a real world program, unless you're doing like i do in my team : i specifically ban all modern concurrency apis in favor of the old ones.
There’s a layering:
- gcd is the lowest level
- async/await lets you write tasks with suspension points (they wait for events to happen); but an async function is still conceptually executed serially
- the Task API let’s you spin up async functions into various arrangements of parallel tasks and wait for those tasks to complete
- ‘async let’ is sugar for creating a Task to compute a single expression
- for async functions and Tasks to share data there is a Sendable model and various static isolation checks
- actors build on everything else; actors are classes whose state is completely isolated to the actor, and all method calls are posted on the actor’s executor
Memory-safe concurrency is hard. There’s a lot of ground to cover to replace the kind of things people do with shared state concurrency, and just actors alone are not enough.
> Compare that to go, where there's (at most) one satisfying way to design the problem in the typesystem. As a sideeffect people have to think more about the problem they're trying to solve, and how to simplify it to the maximum.
I like the aesthetics of small languages too, but I think Go picked the wrong small subset. It reminds me of Java in the 1.x days, for x <= 4, where the language had fundamental expressiveness constraints that created a huge amount of unavoidable boilerplate and type unsafety in even the most basic programming situations.
i was having doubts before go introduced generics. But the fact that they managed to add it so perfectly while maintaining the unambiguous focus on simplicity and structs made me confident they've really nailed it.
The only feature i don't get why they haven't added yet is proper enum to get some kind of ADT. It looks super orthogonal to the language and quite convenient. However at this point i fully trust their taste. If they think it's going to lead to catastrophic design decisions, i fully believe them.
They do, but i'm super relieved they managed to add it to the language while not changing its focus and general feeling.
It really feels like a refactoring tool rather than a new design tool : you still think mostly about functions and structs and don't get carried by "is x a kind of y" kinds of discussion.
It seems to me that their threshold for introducing new language features is extremely low. If something cannot be made to look flawlessly pretty using existing syntax they will go to any length to fix it, regardless of how much extra complexity they have to introduce.
Result builders are a case in point. Essentially, they didn't like the bracket and the comma between array items in the special case where a function returns only a single array.
// So rather than this
let items = makeUgly {
[
Item("One"),
Item("Two"),
Item("Three")
]
}
// It had to look like this
let items = makePretty {
Item("One")
Item("Two")
Item("Three")
}
Yes, it's prettier. There's a bit less clutter. But there's a cost too. makePretty uses entirely different language semantics without any syntactical clue at the call site.
So now there are essentially two separate syntax modes in Swift, and every time you look at some code you have to make up your mind on whether to read it in normal mode or in result builder mode. Sometimes that's easy to know in context. But it's another thing you have to pay attention to at all times.
I feel swift really makes it impossible to learn as a language.
The number of idioms
has grown exponentially at the detriment of orthogonality. I think the red line was crossed with result builders indeed.
Looks like a manager once said "we've got to look like jsx/html, now !" and the poor devs had to obey. It makes absolutely no sense from a language design POV.
It was Chris Lattner that used the phrase "world domination" and would even clarify that although it sounded like a joke, he was fairly serious about it.
Go seems to have been developped by true masters that really understand that less is more.
"The key point here is our programmers are Googlers, they're not researchers. They're typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They're not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt." - Rob Pike
Ignoring complexity on the language level simply passes the buck to application code, and developers end up paying that price across the ecosystem in perpetuity.
They've also kept a laser sharp focus on working on things that really matters and took the time to do things right.
Go's async model is underspecified, its implementation of generics incurs runtime overhead, much of the "wonderful tooling" only exists because of severe shortcomings in the language design and/or compiler implementation, its FFI is... difficult, etc.
To be blunt, the values that Go embraces are none of the values I seek in a programming language or community. And I feel sorry for anyone too heavily invested in Go to turn back.
golang concurrent programming is by far the best i've ever used for its application domain which is mainly infrastructure middlewares running in a connected environment.
The tradeof between language features and maintainability is also IMHO the best in class. Never before have i ever looked at a stdlib sourcecode and told myself "hu, yeah i see how that works".
I'll offer a different take: Thinking that complexity can be offloaded into the language is not seeing the whole picture.
Offloading complexity into the language works for the simple cases, but the more one is trying to fit a solution into a physical box with physical constraints, and the more flexibility is required from the business logic, the situation will change in such a way that more control about the specific of the solution becomes a requirement, and the language shortcuts will fit less and less, to the point where they become a liability. Doubly so if it is difficult or impossible to unwrap the layers of language features.
Of course, many problems can be solved with just a few Python scripts, or even with a huge pile of them. On the other hand, we can find a possible explanation why it could make sense for some teams to code huge apps in low-level languages (C or C++) or languages with few language features (e.g. Golang), or even to try and control the whole stack by maintaining their own language infrastructure.
the situation will change in such a way that more control about the specific of the solution becomes a requirement, and the language shortcuts will fit less and less, to the point where they become a liability.
This is exactly what Go does. Languages like C++ or Rust make it possible to expose and interact with those complex requirements, whereas Go takes it away in the name of “simplification”. Just look at all of the insane hacking tailscale does on Go to make it work for them.
Any example? I honestly don't know tailscale nor have I programmed seriously in Go.
I can understand that sometimes you want tools to quickly cut through obstacles, however I've also noticed that applying such tools without extreme care, like on a regular basis, seems to always lead into local maxima and at some point these choices have to be reconsidered.
One explanation for this phenomenon that I've found is that language features are typically accessed using special syntax, because otherwise they could be library features. Well, it's hard or typically even impossible to abstract over syntactic uses of such features. For example, it's hard to automate the definition of a class based on more abstract concepts, when such creation has to be done as explicit syntax instead of using a builder-style API that iteratively adds members and methods.
I'm using F# via the .NET SDK on macOS (i.e. I could also use C#), so it seems to have less platform lock-in than Swift at this point. It does have its limitations, for instance I can't for the life of me figure out how to make the F# REPL UTF8-capable. As ludicrous as that is, it's not really my use case, so I can live with that for now.
I'm not sure why you're saying Swift is locked in to Apple platforms, it's definitely cross-platform. It's no problem to run Swift on Linux or Windows. Of course, Apple's UI libraries aren't cross-platform. And they're putting a bunch of effort into making it better for servers and such. I wish more people realized how usable it is cross-platform, I think this is a misconception that hampers Swift adoption.
Swift is definitely my favorite programming language from a language standpoint. I really like the way Swift works. It's safe, concise without being terse, and easy to extend.
Sure, some people can argue there's some "bloat", but I think that's inevitable in a language that was designed to immediately be usable within Apple's existing APIs and frameworks. There's going to be complexity with Objective-C interop, C++ interop, etc. But that stuff is necessary for Swift to be usable for big projects. And I think Apple has done quite a good job of evolving Swift to be cleaner, clearer, more ergonomic, etc.
I'm very excited that there's a lot of effort behind an open-source, cross-platform type-safe, compiled language that also prioritizes ergonomic high-level programming.
> I'm not sure why you're saying Swift is locked in to Apple platforms, it's definitely cross-platform
I think the key word in OPs comment is "effectively".
The tools for building cross platform Swift exist and work in various levels but they aren't at the point where you would want to choose Swift over existing cross platform tools.
Lots of open source projects and foundations, or businesses where you can actually pay for guarantees.
Apple just doesn’t like making solid commitments to outsiders, especially for something as mundane as legacy support. They want to mold swift into anything they want/need.
IMO Go would have been 70% better with Result instead of (*type, error). So, so much duplicative code would have been avoided. It's not even a different paradigm. What a wasted opportunity.
It is kind of, given the tooling that is still bound to .NET Framework, or that if you want to analyse profiling data, that is only available in VS/Windows.
Also many .NET shops still only target VS for their SDK plugins.
Additionally, VSCode seems to be getting a new developer experience, also tied to the same licenses as VS and VS4Mac.
Finally, if you really want a cross platform GUI, you're better off with 3rd party like Avalonia and Uno, than anything Microsoft.
This stuff in many shops is what makes them go Java instead of .NET, regardless of .NET Core/.NET 5+, as there are no "yes but" when discussing frameworks versus OS support.
I think that’s fine as long as there’s no performance penalty.
People were doing it anyway. This just eases the friction, and potentially increases performance since many wrappers are really inefficient with regards to data marshalling
The reason why many of us still reach out to C++, is that in many cases the option is only between C or C++, and from both, at least C++ provides some safety knobs.
I’ve been bitten enough by Objective-C++ compiler bugs (Apple’s attempt at mixing Objective-C and C++), as well as bugs in Apple’s code in the Swift ecosystem, not to trust the stability here for at least several years. I also don’t see how this is much better than just creating a C or ObjC wrapper around a C++ library, rather than needing to keep C++‘s complicated language features, and Swift’s interop with it, in my head as I use the library. Were there people clamoring for this? I can’t really see the point.
Going from C++ through ObjC++ to ObjC to Swift is pretty onerous. There’s a lot of type reduction and conversion along the way and a lot of manual binding generation. Performance and safety footguns are plentiful through that route.
If you have large existing C++ libraries, this (in theory) would really help simplify the process and reduce that mental overhead you mention.
I’m not sure how your suggestion of creating wrappers is better because you’d have to still keep the C++ side for those wrappers and introduce a new intermediary layer to manage as well. Surely having one less layer is desirable?
You might have better luck using g++ rather than clang for Objective-C++. We originally developed it on gcc (there was no llvm and the NeXT used gcc). By the time Apple switched to llvm I imagine they weren't as interested in Objective-c++
This is a lot of what folks refer to as compiler bugs when dealing with ObjC++, yeah, usually when they not asking for help and instead just cobbling together their own workarounds.
I don't think I've ever encountered an obj c++-related compiler bug in close to a decade of working on a ~200k loc Obj-C++ project, and we do pretty aggressively mix obj-c and c++ things
It’s more direct is the point. Why add C in between when you can use C++ directly? Like actually, what’s the point of the C in between? It’s only adding yet another thing to maintain when your can just access C++ directly from Swift and vice versa.
On a related topic of hybrid Swift and C++ tooling - has anyone used Doxygen/Sphinx to generate HML docs for modern Swift, similar to how people do it for pure C++ projects?
I’ve never had good luck getting documentation working cross language and so I use docc for Swift, doxygen for C++, rustdoc for Rust and Sphinx for Python. Then I just link between them if a project uses more than one language
Sure, but how do you style and host those docs? Can you style them identically? We use a combo of Furo + Sphinx + Doxygen for C/C++, Python, JavaScript, Java here: https://unum-cloud.github.io/docs/usearch
For ObjC, Swift, Rust, and other languages we couldn’t make it work for now.
I gave up on trying to style them exactly the same. I manage my own CSS that brings them closer but they all behave so differently.
I wrote my own hosting solution to host multiple doc types and switch between versions. Unfortunately I can’t share it since it’s for work but it wasn’t too hard.
Though I wish there was a good off the shelf solution here.
I have not yet understood who this is for. Are there many developers who have Mac apps written in C++ that want to move their code base to Swift? If so how do they do UI? I thought you needed to at least use Objective-C to use Apple GUI frameworks.
Or is there some set of libraries in C++ that are crucial to bring into Swift?
105 comments
[ 3.2 ms ] story [ 183 ms ] threadC++ `class Fern: public Plant` becomes two different types in Swift `struct Plant` and `struct Fern`, how is this supposed to help anyone?
https://www.swift.org/documentation/cxx-interop/#accessing-i...
The idea is that you should be able to use a derived object anywhere the base one is required.
Yes, it won’t be as ergonomic as the ideal, but it’s better than many binding solutions from C++ to compiled languages, while providing most of the usability.
Plus the footnote says that they plan to resolve this in future Swift versions.
Swift and C# are the well-designed languages I'd rather use at my work instead of Go. It's a real shame both are effectively locked into a specific platform.
You can really tell that they had years of attention and careful design by experts in PL poured into them. Go seems like an amateur hobby project in comparison.
Go seems to have been developped by true masters that really understand that less is more. They've also kept a laser sharp focus on working on things that really matters and took the time to do things right.
Swift on the other hand looks like more and more bloated every day, without adressing the main pain points of the experience of coding in swift in the real world.
I also fear Swift is going a step too far. For example, IMO Swift's compilation speed isn’t good enough.
If that’s a matter of lack of tooling, I won’t blame the language, but is it, or is its slowness unavoidable for a language that is so flexible? If the latter, I would rather have a bit less flexibility and more compilation speed.
Similarly, there’s quality of error messages. Swift has gotten better there, but still has some weird ones. Are they unavoidable for the current language?
Swift and Rust both suffer from slower compilation than Go, primarily because LLVM prioritizes optimization over speed.
There are of course other reasons like complexity of language, size of compilation units etc… but optimization tends to be the biggest hit in my experience.
Hence why Rust is adopting/investigating cranelift as a debug compiler to improve speed.
I guess you don’t have much experience with Swift. LLVM code generation may be slower than alternatives, but that’s more or less by a constant factor.
Swift’s type inference, on the other hand, can lead to a combinatorial explosion.
For example, https://www.hackingwithswift.com/articles/11/how-to-make-swi... claims that
takes over 11 seconds to compile, whilecompare this code:
takes about 75ms, and compiles in 71ms.Of course, all of these use LLVM, and likely even generate highly similar intermediate code.
Also, you’ll see these slowdowns even in debug builds that don’t ask LLVM to optimize code much.
But nothing you said contradicts what I said either. Yes it’s easy to get into slow paths with Swift, but it’s also easy not to. Granted it takes a lot of up front knowledge to do so.
But even if you just stick to the LLVM portion, llvms debug mode is slower than cranelift and other toolchains debug modes. Debug doesn’t mean “no optimizations” whatsoever, nor does it mean efficient paths through the toolchain either.
It's been humbling to come back to it over the intervening 7 years and realize they weren't nitpicks that would be resolved shortly, and people would understand if they just read the swift-evolution mailing list. They were fundamental flaws.
This is a Swift-only feature in major languages with type inference.
One example that would improve the above: replace module imports with file specific imports, including within the module itself. You could also make a case for operator overloading and there are probably a bunch of candidates a swift compiler engineer could tell you that would be small but significant like that.
https://github.com/apple/swift/blob/main/docs/CompilerPerfor...
Xcode also trips on simple type errors and sometimes fails to even provide a line number where the error lies. As someone who just wrote a Swift app after no prior exposure, it's oddly immature in some basic ways after nearly a decade from launch.
I find every other aspect of "developer experience" excellent
Tools like Agda, Spark, Idris, and Coq move the tradeoff around towards "probably correct", and I welcome seeing some of their features become gradually available. Getting things right the first time is useful.
But these features can also make it hard to get anything done quickly.
Good developer experience means finding a balance between the quick and dirty, and the provably correct.
* Language features that make it so that you don't have to write code at all
* Tooling that makes it easier to find bugs in the code you do write
* Expressiveness and portability that allows the code to be used in more places
* Approachability, so that new people can pick up the language quickly
* "Batteries", so that people can rely on your high-quality libraries instead of having to manage their own
* Documentation and community, to handle the hardest parts of programming: people
* Performance, so that the code can use fewer resources
…among many other things.
With exception of Kubernetes, most of Google's software keeps being written in Java, C++, Kotlin, and nowadays Rust.
Its use is much more widespread outside Mountain View walls than inside them.
Google's software runs on Borg, which is _not_ written in GoLang.
Google's three OS projects all eschew GoLang as well.
1. Borg precedes K8s and likely is tightly coupled with Google's backend infra - that's to say, Borg gets architected around Google's existing workflow and new backend development is written around Borg's workflow.
2. GoLang was never intended to be an OS-level programming language. It was created to enable more robust, efficient, and rapid development in a particular space. It would be just as silly to argue that Google's three OS projects all eschew Dart.
1. is fair.
2. I'd argue is false, given Fuschia and Dart are tightly integrated (as another commenter noted) and that GoLang was originally designed as a C++ replacement born from the Plan9/Inferno tool chains (certainly "OS-level"). Also there's a ton of non-kernel code in an OS so this is a really blurry distinction. containerd does some pretty low level stuff with Linux and GoLang is perfectly capable of this.. if Fuschia wanted to use it they could.
They were however rewritten in Rust, after the author left Google and they wanted to clean Fuchsia from Go code.
Go was created by three folks that got fed up waiting on C++ compile times, and from their point of view Go is designed for people unable to take feature rich languages, on Rob Pike's own words.
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt."
Or alternatively,
"It must be familiar, roughly C-like. Programmers working at Google are early in their careers and are most familiar with procedural languages, particularly from the C family. The need to get programmers productive quickly in a new language means that the language cannot be too radical."
Another issue would be a coherent concurrency system. Today we have a mix of gcd and actor system, declarative task, async await, etc which when put together makes me totally unable to understand what's going on in a real world program, unless you're doing like i do in my team : i specifically ban all modern concurrency apis in favor of the old ones.
The same could be said with protocol extension + generics + class + structs.
For each problem, there are approximately 4 possible designs with subtle tradeoffs. Swift team advocates for "expressiveness". My personnal experience is that all patterns and technics will ultimately end up showing in the codebase, depending on the mood of the developer.
In the end, i've started to do like C++ developers end up doing : write guidelines on which language features are allowed, and which are forbidden.
Compare that to go, where there's (at most) one satisfying way to design the problem in the typesystem. As a sideeffect people have to think more about the problem they're trying to solve, and how to simplify it to the maximum.
The end result is that go pushes you toward simple and maintainable code. Swift pushes you toward being fancy for no good reasons.
you are referring to basic languauge features... you consider classes and structs pain points? these are the basic building blocks of the language, if you find them painful this is definitely not the language for you.
It looks great on paper and do enable beautiful designs in some cases. But after tasting languages like go you realize you can get away with struct & functions in 99% of the cases, and that having less design options is actually a plus. It lets you focus on what really matters : domain modeling.
(a) Foundation is portable and available on Windows and Linux.
(b) You are confusing language features and OS features. Async/await is part of the language. GCD is part of the OS.
(c) Banning modern concurrency primitives is not viable forever — you’ll be left behind as the native APIs adopt them.
I wouldn't want to be the guy relying on this table advancement for his own project. The fact that they're rewriting it in pure swift probably says a lot about the quality of the current approach.
b: makes absolutely no difference from a developer perspective. if you want to run threads in swift you're going to use gcd.
c: my take with all apple software tech has been to wait until they've dogfooded their own tech long enough to make it useable. Worked very well for me so far, thank you very much.
There’s a layering:
- gcd is the lowest level
- async/await lets you write tasks with suspension points (they wait for events to happen); but an async function is still conceptually executed serially
- the Task API let’s you spin up async functions into various arrangements of parallel tasks and wait for those tasks to complete
- ‘async let’ is sugar for creating a Task to compute a single expression
- for async functions and Tasks to share data there is a Sendable model and various static isolation checks
- actors build on everything else; actors are classes whose state is completely isolated to the actor, and all method calls are posted on the actor’s executor
Memory-safe concurrency is hard. There’s a lot of ground to cover to replace the kind of things people do with shared state concurrency, and just actors alone are not enough.
> Compare that to go, where there's (at most) one satisfying way to design the problem in the typesystem. As a sideeffect people have to think more about the problem they're trying to solve, and how to simplify it to the maximum.
I like the aesthetics of small languages too, but I think Go picked the wrong small subset. It reminds me of Java in the 1.x days, for x <= 4, where the language had fundamental expressiveness constraints that created a huge amount of unavoidable boilerplate and type unsafety in even the most basic programming situations.
i was having doubts before go introduced generics. But the fact that they managed to add it so perfectly while maintaining the unambiguous focus on simplicity and structs made me confident they've really nailed it.
The only feature i don't get why they haven't added yet is proper enum to get some kind of ADT. It looks super orthogonal to the language and quite convenient. However at this point i fully trust their taste. If they think it's going to lead to catastrophic design decisions, i fully believe them.
It really feels like a refactoring tool rather than a new design tool : you still think mostly about functions and structs and don't get carried by "is x a kind of y" kinds of discussion.
Result builders are a case in point. Essentially, they didn't like the bracket and the comma between array items in the special case where a function returns only a single array.
Yes, it's prettier. There's a bit less clutter. But there's a cost too. makePretty uses entirely different language semantics without any syntactical clue at the call site.So now there are essentially two separate syntax modes in Swift, and every time you look at some code you have to make up your mind on whether to read it in normal mode or in result builder mode. Sometimes that's easy to know in context. But it's another thing you have to pay attention to at all times.
Looks like a manager once said "we've got to look like jsx/html, now !" and the poor devs had to obey. It makes absolutely no sense from a language design POV.
I guess you never looked into Kubernetes and their blobs of generated code to work around Go's weaknesses.
As such it should provide comparable level of flexibility, tooling and developer power.
Anything else isn't part of swift focus.
However i have yet to see any swift codebase of comparable complexity. So, the jury is still out on which language enable the largest scale.
"The key point here is our programmers are Googlers, they're not researchers. They're typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They're not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt." - Rob Pike
Ignoring complexity on the language level simply passes the buck to application code, and developers end up paying that price across the ecosystem in perpetuity.
They've also kept a laser sharp focus on working on things that really matters and took the time to do things right.
Go's async model is underspecified, its implementation of generics incurs runtime overhead, much of the "wonderful tooling" only exists because of severe shortcomings in the language design and/or compiler implementation, its FFI is... difficult, etc.
To be blunt, the values that Go embraces are none of the values I seek in a programming language or community. And I feel sorry for anyone too heavily invested in Go to turn back.
The tradeof between language features and maintainability is also IMHO the best in class. Never before have i ever looked at a stdlib sourcecode and told myself "hu, yeah i see how that works".
Offloading complexity into the language works for the simple cases, but the more one is trying to fit a solution into a physical box with physical constraints, and the more flexibility is required from the business logic, the situation will change in such a way that more control about the specific of the solution becomes a requirement, and the language shortcuts will fit less and less, to the point where they become a liability. Doubly so if it is difficult or impossible to unwrap the layers of language features.
Of course, many problems can be solved with just a few Python scripts, or even with a huge pile of them. On the other hand, we can find a possible explanation why it could make sense for some teams to code huge apps in low-level languages (C or C++) or languages with few language features (e.g. Golang), or even to try and control the whole stack by maintaining their own language infrastructure.
This is exactly what Go does. Languages like C++ or Rust make it possible to expose and interact with those complex requirements, whereas Go takes it away in the name of “simplification”. Just look at all of the insane hacking tailscale does on Go to make it work for them.
I can understand that sometimes you want tools to quickly cut through obstacles, however I've also noticed that applying such tools without extreme care, like on a regular basis, seems to always lead into local maxima and at some point these choices have to be reconsidered.
One explanation for this phenomenon that I've found is that language features are typically accessed using special syntax, because otherwise they could be library features. Well, it's hard or typically even impossible to abstract over syntactic uses of such features. For example, it's hard to automate the definition of a class based on more abstract concepts, when such creation has to be done as explicit syntax instead of using a builder-style API that iteratively adds members and methods.
Swift is definitely my favorite programming language from a language standpoint. I really like the way Swift works. It's safe, concise without being terse, and easy to extend.
Sure, some people can argue there's some "bloat", but I think that's inevitable in a language that was designed to immediately be usable within Apple's existing APIs and frameworks. There's going to be complexity with Objective-C interop, C++ interop, etc. But that stuff is necessary for Swift to be usable for big projects. And I think Apple has done quite a good job of evolving Swift to be cleaner, clearer, more ergonomic, etc.
I'm very excited that there's a lot of effort behind an open-source, cross-platform type-safe, compiled language that also prioritizes ergonomic high-level programming.
I think the key word in OPs comment is "effectively".
The tools for building cross platform Swift exist and work in various levels but they aren't at the point where you would want to choose Swift over existing cross platform tools.
Apple just doesn’t like making solid commitments to outsiders, especially for something as mundane as legacy support. They want to mold swift into anything they want/need.
This view of C# will be wrong for a almost a decade very soon! (if you don’t count mono)
Also many .NET shops still only target VS for their SDK plugins.
Additionally, VSCode seems to be getting a new developer experience, also tied to the same licenses as VS and VS4Mac.
Finally, if you really want a cross platform GUI, you're better off with 3rd party like Avalonia and Uno, than anything Microsoft.
This stuff in many shops is what makes them go Java instead of .NET, regardless of .NET Core/.NET 5+, as there are no "yes but" when discussing frameworks versus OS support.
People were doing it anyway. This just eases the friction, and potentially increases performance since many wrappers are really inefficient with regards to data marshalling
If you have large existing C++ libraries, this (in theory) would really help simplify the process and reduce that mental overhead you mention.
I’m not sure how your suggestion of creating wrappers is better because you’d have to still keep the C++ side for those wrappers and introduce a new intermediary layer to manage as well. Surely having one less layer is desirable?
At WWDC 2023 there were a couple of sessions with Objective-C++ content, e.g. how to extend PyTorch on macOS.
https://swift.org/blog/swift-docc/
I’ve never had good luck getting documentation working cross language and so I use docc for Swift, doxygen for C++, rustdoc for Rust and Sphinx for Python. Then I just link between them if a project uses more than one language
For ObjC, Swift, Rust, and other languages we couldn’t make it work for now.
I wrote my own hosting solution to host multiple doc types and switch between versions. Unfortunately I can’t share it since it’s for work but it wasn’t too hard.
Though I wish there was a good off the shelf solution here.
Or is there some set of libraries in C++ that are crucial to bring into Swift?
Now with more and more pure Swift apps, the C++ interop is more direct than having to go through Swift’s existing C interop.