Swift 6
swift.org
swift.org
Swift is caught between two clans: the Swift Working Group™ open-source community, and the Apple corporate entity who pays most of their salaries. Both have their own incentives and their own imperfections, but you guess who has the majority influence.
Ridiculous, permanent, tech debt such as hardcoded compiler exceptions are permanently living in the compiler codebase. Even worse, half-baked concepts such as result builders are pushed through without any real discussion because Apple wants the SwiftUI syntax to look pretty.
It's an amazing language still, but I can't see it surviving as nicely in the next 10 years if Apple doesn't learn to let go.
* https://stackoverflow.com/questions/34540185/how-to-convert-... https://stackoverflow.com/questions/32305891/index-of-a-subs... https://stackoverflow.com/questions/39677330/how-does-string... https://stackoverflow.com/questions/24250938/swift-pass-arra...
You can get a `utf8` or `utf16` "view" of a string, and index it like normal array (`myString.utf8[0]` gets the first utf8 character). But it's not going to work with complicated emoji, or different languages that may have representations into utf16, etc. Again, I think the vast majority of people don't care for complete correctness across all possible string representations, and possibly Swift has gone too far here — as noted by all the Stack Overflow posts and clunky API
On the array-pass-by-reference, I'd argue that it's valuable to learn those semantics and they aren't particularly complicated. They offer huge benefits relating to bugs caused by unintentionally shared state. Marking the parameter `inout` is a small price to pay, and really forces you to be explicit about your intentions
About structs and pointers, I'm familiar with them in C/C++ where the syntax is clear and consistent. It's not consistent in Swift. And arrays don't even act like regular structs, I forgot: https://stackoverflow.com/questions/24450284/conflicting-def...
The top answer to your third question gives a pretty good explanation of why Swift string indices work the way they do (as well as showing nicer ways to spell a lot of the operations on them), which mostly addresses the first and third questions. It really seems that your last link is just asking for the `inout` modifier; I'm not sure why that one is especially confusing.
Obviously, there's always stuff that can be further improved, but none of these are especially onerous (once you get past the "string indices are not just integers" step, at least--for people who really just want to pretend the world uses ASCII or work with UTF8 bytes directly, string.utf8 may be an nicer interface to use).
The time I was doing a lot of string manipulation in a team Swift project, we had to write our own wrapper that basically stored strings as arrays of printable characters because the native one was too annoying. This also protected us from all the breaking changes Apple made to strings across Swift versions.
The inout one is different. It's confusing that arrays and dicts are structs, which have different rules from regular objects, and the syntax takes some getting used to:
func addItem(_ localArr: inout [Int]) {
localArr.append(4)
}As a long-time assembly and C programmer and now Swift programmer, I would say that structs _are_ regular objects, and things with reference semantics are weird. It all depends on your point of view!
Edit: Just remembered that arrays still don't act like regular structs either! https://stackoverflow.com/questions/24450284/conflicting-def...
The philosophy of making code look "clean" at the cost of hiding critical information in some far away place is the biggest mistake in programming language design. If code happens to look clean it should be the result of being simple rather than being cleansed for effect.
Other bad ideas in the same vein are computed properties and function builders.
They’ve been used for more than SwiftUI by now. The Regex support since 5.7 uses them: https://www.hackingwithswift.com/swift/5.7/regexes
I thought maybe that was all fixed by now, but guess not?
But it's very true that the state of the language can be felt in their native apps, that tend to suck pretty bad recently. I still can't get over the nightmare that is the split up of iTunes; at least we knew that it was clunky because of old age, the new stuff is just bad.
https://anthony.noided.media/blog/haskell/programming/2020/0...
Something like Idris or Coq would have even more complex messages, though I don't have an example on hand.
A little search-engining didn’t surface info about this, could you point me in the right direction?
It would certainly take a while, but could be done if Swift is aiming to be a mainstream, long-term, cross-platform language - unless this kind of thing is just not done, or there is some technical aspect I am missing?
Can you think of any other languages that share a duality like Swift? I mainly play in the python ecosystem, but I am looking to branch out and really learn a compiled language. What you wrote about Swift makes sense and would be concerning if I had recently picked it up.
"Yadda, yadda..." regarding picking the right tool for the job aside, I don't want to waste time on a language that can be usurped by a giant corporate borg anytime they see fit.
Tip from a friend - just learn C++. It's not sexy, it's not new-fangled, but it's rock solid, runs half the world, and you will never struggle to find a job.
You'll also understand what it is that all of the new-langs are trying to improve upon, and will be able to made an educated judgment about whether they succeed or not.
A good resource for modern C++ (albeit it seems to be down rn?) is https://www.learncpp.com/. I'm not affiliated, it's just good.
I would also not use "rock solid" in comparison to how easy it is to hit undefined behavior.
Used all over and easy to find jobs, yes.
I find that a lot of the newlangs take the view that since most programming only uses 20% of the toolkit, they can just dispense with the remaining 80%. Which is great, until you discover that virtually every sophisticated project needed one of those things from that remaining 80%, and they all needed a different one. A nice language for writing 'Hello world's isn't going to cut it. And so either the language insists on its simplicity and stagnates, or it just reinvents all the complexity of C++.
At which point, you were better off just taking the garage full of tools that you already had, rather than going all in on some newlang, getting blocked, and stalking a github ticket for years waiting for the lang to get that feature you need. (If you spent the time in C++ instead, its full garage wouldn't look so imposing over time!)
What's the famous quote? 'There are only two kinds of languages: the ones people complain about and the ones nobody uses.' :P
Re generics, aren't C++'s virtually the same as Rust's? Especially now that C++ has 'concepts'?
> Re generics, aren't C++'s virtually the same as Rust's? Especially now that C++ has 'concepts'?
I'm not worried about generics when I talk about template nightmares, that's more about rvalue and const overloads and vararg templates and careful manipulation of SFINAE, all coming together.
This is just a symptom of its age. C# has this same problem, too, and it's only getting worse.
Actually, regarding sophisticated projects, there’s quite some complicated projects that succeed without C++ power, like Postgres and Python.
The question was about the first compiled language someone should learn, and for that, C++ is great. It's going to cover most of the use cases for compiled languages, while providing relatively familiar abstractions and semantics.
C is fantastic when you need to eke out every single cycle of performance, which is why it's a great choice for Python and Postgres. But you do this by effectively writing assembly instructions, which is why it's a terrible choice for someone coming to compiled languages for the first time.
C++ gives you equivalent performance to C, for a fraction of the effort, in about 90% of the use cases. For the remaining use cases, C is a better (and often only) choice, but no one who is only learning compiled languages for the first time is anywhere near being able to write that kind of C code. (After a few years of C++ they'll be in a much better place to do so!)
Just getting good and greasy with Python and JS with all the typical libs has been more rewarding for me. Nobody taught me, but it was useful.
Is that good advice for certain domains only? For example, you likely wouldn't want to use C++ for web server backend? You could, but may not want to.
But if you're looking to learn a compiled language - presumably because you want to write applications, games, or systems - then C++ is a great one to learn.
But the reality is that many major languages already have very outsized corporate influence. Either at the language level or the compiler level.
Swift is open source and has been separating from Apple ownership as of this year.
"Oracle, IBM, Azul, AWS, Google..... this and that."
"Microsoft this and that."
Chris Lattner
It's just shocking to me that the compiler is asking me to help it out.
EDIT: Meanwhile, over here in go-land, I just got annoyed that adding a new external authorization library took the average compile time of my 20k project from 7s to 9s.
Yet rewriting components in AppKit/UIKit feels regressive since even Apple's docs migrated to SwiftUI, but sometimes that was the right answer to regain some control over what was going on.
Though some of that was limited to macOS which seems to get a lot less attention from Apple than iOS, or because you're naturally building more complex layouts on macOS. But it always makes me appreciate how much better the kit is for building web frontends.
The problem with SwiftUI on macOS in my opinion is that it's just not well-suited to the types of layouts common on desktop and especially macOS. It's best at uniform grids and lists which is more of a mobile thing. On macOS you want things like visually balanced whitespace, optical centering of labels+controls in forms, etc which are often rather arbitrary and exactly the thing that SwiftUI is worst at.
It will go so far as to suggest a change in a thing only for the developer to find out the cause was completely unrelated or even in a model in a different file.
Helpful error messages for SwiftUI would go a long way
In what other industry does anyone use text to create visual layouts? We still do drag and drop to design the layouts anyway, followed by re-creating the drag and drop version... using text...
It's all quite maddening if you try and make sense of it.
But the actual implementation of SwiftUI is terrible. Long compilation times, cryptic compiler error messages, bad performance, and a baked-in two-way binding observable state API that's great for code snippets and WWDC talks but makes building a coherent state model in a real app very difficult add up to a big mess.
Most developers I know in the ecosystem tried and tried to like the visual editor, but ended up falling back to code. Why? Source code is pretty great. It’s easy to read, write, share, diff, and organize.
SwiftUI has, again, a very modern and interesting take on visual editing. The code is the truth, but there’s a rich, realtime preview canvas that makes rountripping possible. For reasons, it’s unfortunately all but unusable for a lot of folks with complex Xcode projects, but it’s a very good idea.
In summary: Visual layout editors and text are both pretty great. The devil’s in the details.
If IB's performance and stability issues could be fixed, I think its biggest problem is the near-unintelligible spaghetti XML that it generates for XIBs and storyboards, which is a huge pain for VCS and frequently cause of merge conflicts (which must be manually corrected). If they just cleaned up the XML output or switched to something more friendly to manual editing that'd help a lot.
xibs and storyboards were also XML but IB would insert attributes that had no visual impact, move lines of XML around simply upon opening the file, or randomly change x/y values of the viewport upon opening the file, or update float values to use 15 decimal places instead of 2, and so on. Apple's insistence on making the visual editor have priority over text led to a worse developer experience as teams grew.
Miss a semicolon, every line after but not including the one with the error.
ResEdit was better in the 90s than SwiftUI was in Xcode last year. Hoping to find that standard will be re-achieved when I install the update…
Yeah, this sucks, but it's also mind-boggling. The SwiftUI body result builder returns "some View" which means the compiler has to infer the specific type that the function produces, as a complex combination of whatever different if/then and while loops, along with their transitive function applications and closure contexts (where the type inference can be outside-in instead of inside-out).
Then layer in in the @Observation macro magic for observing data types and re-building views when data changes, Swift Data pulling with fully typed predicates from a data store automatically replicated across devices....
It's like rocket thrusters from every angle, and pretty easy to start tumbling off the happy path.
Swift 6 is the first version with enough low-level improvements and cross-platform capabilities to make me curious if the Swift team is trying to aim for long-term replacing C, C++, Rust, Zig, etc.
I don't get how people still get surprised by this.
I haven’t given 6.0 a full effort yet, but a couple test compiles without any tuning showed only incremental improvements.
O'Leary's Law of Swift Comments on HN, much like Betteridge's Law of Headlines, says the answer to Swift speculation on HN is always no.
If there is any global aim, it is to migrate internal teams still working in ObjC/C++ to Swift.
If this sounds close-minded, it's worth talking with an iOS developer 1:1 and get info on the day-to-day experience of SwiftUI 5 years in, and concurrency/actors 3 years in. They have a lot of fires to fight to keep the house standing.
Apple's stated goal is to make Swift a viable language up and down the technology stack, which goes way beyond Objective-C. They are actively working on making Swift viable in baremetal environments, firmware, drivers, etc. IIRC they even referred to it as a C++ successor language at WWDC this year.
I agree that they're not trying to "replacing every other programming language," but they're investing in it becoming a viable language choice in most environments.
Selfishly I wish mixed C targets were a priority before C++ but I get why this was more important to them
Nice!
I'd gently warn against parsing that too closely: having been in the community and the weeds starting in 2014(!).
ex. the cross platform Foundation was announced and open sourced in 2016.
I'm sure a lot of things were upgraded or more consistent and its better etc., but it's a painful to remember what was expected + communicated at the time, and how much later it is.
Right now, my Mac app depends on a few simple web services that are written in a different language. It would be neat if those services could be written in Swift, so that I could just use the language I already know instead of relearning how to do decode JSON in Python/Ruby/PHP.
Swift on the server doesn't have to become widely used. As long as there is a simple way to write basic web services, it is useful.
Then somebody else would have to now learn Swift even thought they write in different language like: Android Developers (Kotlin/Java), React Native devs (JS/TS ), Windows Devs, Linux Devs etc. As long as Apple don't invest more in official cross-platform tooling Swift is not gonna be mainstream (even though I like Swift). They have to bless other competing platforms.
If you have a bigger team, then it doesn't matter as much, because you have different people who do the iOS app, people who do the website, people who do the back end etc.
Swift on the server doesn't have to go mainstream to be useful. If you just need some basic web service that syncs highscores or verifies in-app purchases then it would be neat if you could write that in the same language as the app itself.
I don’t think Swift has a place in these niches FWIW. Writing low-level Swift code is very verbose and unnatural. Personally I just don’t think it’s practical to have a single language excel across the stack.
It’s already been used in their libraries, their OSes, and even firmware on embedded processors.
This is the way forward. And they’re dogfooding it. Even in some of their embedded processors.
Objective-C was in the kernel.
Objective-C requires a runtime. I thought the kernel was always C/C++.
The runtime support that's required for Objective-C is extremely minimal. In the end you need objc_msgSend() and some runtime structures so that function works.
The methods themselves are just C functions with slightly funky names.
On NeXTStep / OPENSTEP kernel drivers could be and were written in Objective-C.
https://www.nextcomputers.org/files/manuals/nd/OperatingSyst...
To me it kind of feels like swift's place here is going to replace the subset of c++ that apple is mostly interested in. But not necessarily the c++ that they aren't and then likely not the c++ that rust et al are able to replace (although I guess we'll see what they have in mind as time goes by).
I suspect they'll be disappointed if they try to replace c++ in totality. As the end result will likely not be particularly habitable.
Why is that? That is their aim, I think they’ve made it clear.
They’re writing all-ish new code in Swift (not always practical in existing code) from what I understand. They’re already using it for the code that runs in places like the Secure Enclave or other embedded processors.
Can it replace C++ 100% everywhere today? Probably not. I don’t know enough to know why. But that absolutely appears to be the goal.
People keep doing that, because I guess they hatred for Java and Kotlin is higher than the pain of using NDK.
Kotlin’s real strength is the compilers ability to target as many different backends as you want and cross compile into any platform you want. I have an app in production that save for about three lines of swift that declares the view controller for iOS specifically, shares its entire codebase all in kotlin between Android, iOS, Mac, Linux, and Windows. I could add a line to the build file and add web as a target and turn it into a web app.
I feel like “systems programming” is getting increasingly ill-defined these days, anyway. If Kotlin and Java aren’t allowed, how about Go? Are you ruling out all languages with garbage collection?
Currently that’s C, Rust, and some assembly.. obviously there are more languages used for systems programming tasks too. Memory management, concurrency, and low-level access are also super important in a system language. How would you define a systems language?
It doesn't really need to mention "systems" specifically, since the comparison to other common systems languages, in addition to the words "low-level" implicitly imply systems programming languages.
Usually, languages with GC will not be considered contenders for systems programming.
I've been using C#/.Net lately and I've been very impressed. Very large ecosystem and community. Perhaps larger than Kotlin. And you are not stuck with one IDE from one company.
Microsoft also has dotnet MAUI that sounds similar to Compose Multiplatform. I have not used neither so I can't speak to any strengths or weaknesses in the UI libraries.
I’m amazed people like C# so much. I think it really shows its age when you compare it to Swift or another modern language.
Some things I’ve been frustrated with:
- throwing isn’t a contract made at the function level. Anything can throw, and you can ignore it if you want. And the APIs are inconsistent because of it
- nullable types and default values are weird and seem like two solutions to the same problem
- Blazor bindings are very boilerplate heavy
- hot reload doesn’t work most of the time, and iteration times are bad compared to every other stack I’ve used
On the more minor side of things, Swift's built in JSON serialization is super handy. It's really nice to not have to increment the dependency counter to parse JSON and makes spinning up projects that much faster.
Kotlin is nowhere close to Java in this comparison. For all intents, there is just one realistic IDE for Kotlin. And realistically only one build system. And the community is very small. Java, by comparison, has many IDEs to choose from, many build systems, and a very large community to answer questions.
K
That’s what i thought and rewrote my cli util in swift. Ran great on macOS, tried to build for windows and found out there’s no well maintained, actively developed http server for windows for swift.
Dont let these wooing crowds fool you
If you like having an IDE instead of scrolling multi-page compiler error dumps in your terminal window - this is a complete non-starter.
The leading Swift web framework (Vapor) suggests you use Docker to build for Linux. I gave it an honest try - their empty starter 'hello world' web server takes more than a minute to compile. Ok, but surely it'll be faster after I make a one liner change? No - their docker workflow has 0 compiler caching - you'll be waiting more than a minute every time.
Complete non-starter.
I ended up installing a VM, installing the swift compiler and that only takes 2-3 seconds to re-compile a 1 liner change (in a print statement, in an empty project, lol). Consider me very deeply unimpressed.
By comparison - a visual studio code + docker + python/ruby/javascript setup is a well oiled, working machine.
If yes, please show me the way because I've failed and I've given it an earnest try.
Under the hood, Swift-NIO and async Swift is a pretty powerful basis for writing performant servers. Aside from Vapor, there are other small/fast containers like hummingbird.
Not mentioned (surprisingly) is Swift support for wasm/wasi, for deploying code directly in the browser.
Also, "some say" that macros could revolutionize both static and dynamic generation by moving a good portion of site generation to compile time. I'm not aware of any libraries realizing that promise yet.
Finally, Swift concurrent actors have supported distribution for some time, so you can have all-Swift distributed systems, where the client code works with both local and remote servers.
Reference counting is garbage collection, and it performs significantly worse from a throughput perspective than tracing GC, which is possibly the most common metric for web server type workloads.
It really is not nitpicking, we should just really use tracing GC when we mean it.
is reference counting not considered a form of garbage collection?
Also some the Mono issues were related that they never had Microsoft's budget for implementing a bleeding edge runtime.
From Computer Science point of view RC is and will stay a GC algorithm.
https://gchandbook.org/contents.html
https://sites.cs.ucsb.edu/~ckrintz/racelab/gc/papers/levanon...
https://sites.cs.ucsb.edu/~ckrintz/racelab/gc/papers/AzaPet-...
Also, is it “pause execution and clean up” together? As ref counting obviously has to clean up, that’s the whole point - and it actually does so by blocking the mutator thread (the actual program written by the user). Then we didn’t even get to the point where syncing counters across threads are possibly the slowest primitive operation a CPU can do, so if we can’t know that an object will only ever be accessed from a single thread, ref counting has plenty shortcomings. Oh also, nulling the counter in case of a big object graph will pause execution for considerable amount of time (particularly noticeable in case of a c++ program exiting which uses a bunch of shared ptrs)
Swift lacking proper performant GC is a disadvantage. Upcoming features solve it by likely enabling more scenarios to sidestep ARC, but their impact on the Swift as a whole, and user applications that use them, is yet to be seen.
It's important to always remember - there's no free lunch.
I'm sad that Miguel de Icaza seems to have a bone to pick with C# nowadays, but it's not surprising given Xamarin story.
By which you mean "less CPU cycles on a desktop machine with plenty of memory"?
That's not when ARC is more performant; it's better on smaller devices that are under memory pressure and have swapped out some of your memory. In which case you have to swap it back in to go scan for pointers. And if you're a low-priority daemon then you evict higher priority pages in the process.
It's worth noting many others also consider automatic reference counting to be a form of gc, albeit one with different strengths and weaknesses than stack- and heap-scanning varieties
Doesn't it use ARC by default?
binary-trees is almost completely dominated by the time spent in allocator code, and stresses its throughput. This benchmark showcases how big of a gap is between manual per-thread arenas, then tracing generational multi-heap GCs, then ARC and more specialized designs like Go GC. Honorable mention goes to BEAM which also showcases excellent throughput by having process-level independent GCs, in this case resembling the behavior of .NET's and OpenJDK GC implementations.
https://github.com/sh3244/swift-vs-python
Shows a huge difference, as expected for a typed memory-safe compiled language using LLVM versus an interpreted language with a global interpreter lock.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
All JVM languages are not viable by definition for this domain. Object oriented and heavily abstracted nature of the underlying runtime implementations prevents their effective usage in systems programming due to lack of fast FFI, structs, particularly so of custom layout, and the historical aversion of the ecosystem to low-level features.
Kotlin native does not count because presently it has 0.1-0.001x performance of OpenJDK, it is that bad, and I assume is subject to the common subset of features that must also be expressible with JVM.
.NET, especially with compilation to native statically linked binaries (NativeAOT) is an option, and I believe, due to ecosystem maturity as well as very heavy focus on performance in all recent .NET versions as well as continued improvement of low-level features (portable SIMD, byref pointers with simple lifetime analysis, static linking with C/C++/Rust/etc.), it is a strong contender. Unlike Java, C# has great systems programming story, after all, it was influenced as much by C++ as it was by Java, sadly many only ever think about the latter.
However, I'm looking forward to Swift 6. Once it is out, I'd love to see it offer more opportunities at ensuring static dispatch and generic monomorphization (in .NET, generics with struct arguments are always monomorphized like in Rust, so you have tools for zero-cost abstractions) and happy paths allowing to bypass prohibitive cost of ARC with new annotations. By using LLVM, Swift has theoretically great performance ceiling, even if it does not deliver on it just yet, losing to C# by a good margin on the more complicated code due to ARC and dynamic dispatch. But because Apple seems to be invested in using it for tasks that will require addressing these shortcomings, it is pretty exciting to see where they will take it.
(Yeah, this is a dumb question but I'm asking anyway)
Apple did the hard work, https://github.com/apple/swift-nio.
If you just want a big framework to launch an API, that's been around for years: https://vapor.codes
Interestingly though, a trivial HTTP server is actually very easy to implement as well. A very crude HTTP/1.0 server (or maybe even a limited scope HTTP/1.1 server) can actually make for a fun afternoon project. Like minimal (or no) concurrency support, all TCP connections closed after the request/response cycle, GET only (or maybe POST only), etc.
So it's a mixed bag of what you want and how you define an HTTP server.
(I also think all HTTP services should if possible be written in a shared-nothing CGI type language, and not embedded in your random stateful program. This way you can't accidentally leak info across user sessions.)
Also, your comment about "shared-nothing" is interesting too. It surely doesn't hurt to think about it in this way, but likewise, might be out of scope for a simple web server concept (for example, if you're not even really supporting sessions at all).
Basically the difference is that Swift's is more implicit, happens more at runtime, and it will make some programs work via copy-on-write that Rust would reject.
So that's obviously more limiting. It's more flexible when you can allocate memory freely, but it doesn't work if you can't.
Bingo, that's the difference. That's why I said "compile-time memory safety". This is what Rust gives you for your trouble, zero (runtime) cost for memory safety.
Ref counting by default makes most programs easier to deal with.
Guard let (though recently somewhat introduced in rust) are much more friendly ways to unwrap optionals.
Being able to provide parameter defaults and aliases.
Easy passing in of callbacks. Easier async.
Null chains with the Question mark operator.
I really like rust, but it’s much faster to get to a working program in Swift.
And with the new CXX interop, I now reach for Swift by default since I have to deal with a lot of C++ libraries.
I was on board until this one. Async is a rough spot for rust, but I find the async strategy swift went with absolutely baffling and difficult to reason about.
Really depends on the context. I really, really, really hated this instinct in the ruby on rails community when I was still doing that. Magic is nice until it doesn't work the way you expect, which is when it becomes an active liability.
I really don't spend much time thinking about memory management in Rust, but I can certainly understand why one might be happy to not have to design around ownership and lifetimes. I really like the explicit nature of it, though, makes it super easy to read and reason about code you've never seen before.
https://hachyderm.io/@evanw/109859384302551859
https://www.cocoawithlove.com/blog/2016/07/12/type-checker-i...
https://danielchasehooper.com/posts/why-swift-is-slow/
This isn't mentioned at all in the announcement so I'm kind of disappointed.
[0]: https://youtu.be/ENviIxDTmUA?si=FavjcK8IQygnlIwT&t=4417
Swift type inference is two-way, so you can often improve things by annotating types instead of using inference.
Also, it very easy to have complex builders and nested closures that complicate inferencing, so it's a bit of a side-effect of how much people use the powerful features.
The big compile-time gotcha now is using macros. To be safe, each macro runs in its own process hogging cores, and the new Xcode compile scheduler isn't quite sensitive to that, so sometimes command-line builds with less parallelism are a fair bit faster. The solution there is judicious use, particularly of libraries depending heavily on macros.
Last time I converted the swift compiler from the Ubuntu package to work on Debian, stuff was looking really awry. Most things work but not simple things like sigterm signals.
Swift is a fantastic language. I think the most advanced and smart language today. And I say this having used over 20 professionally over 25 years.
Just look at how swiftUI is implemented. It's not even a DSL, it's Swift naturally! Compare it to flutter and you'll see how incredible it is. (I do like dart too though)
As for the language it's full of clever features and advanced ideas that don't suck to use and consider the developer real world use of the language.
Two things really suck in swift though; compiler error messages are straight out of the university of assholery and documentation was crafted in Mordor probably.
Of course most libraries probably won't work well on Linux yet but there is a future with the right balance between safety and speed and joy of developing.
SwiftUI initially promoted their ReactiveX alternative.
Moving to Swift-6 mode with full data-race safety checks can be daunting. They wrote a separate post on that, and Holly telegraphed that they're still actively reducing noise, i.e., warnings where the compiler could use a bit more analysis to prove there's no race.
The really nice thing is you can use the new tooling, but stay with the 5.10 version of the language that works for your code, and feature-by-feature add Swift 6 checking. You can build the same package under both language modes, so libraries can move ahead in their versioning while still supporting clients who are not ready.
It’s hard to say at this point though, adoption might get a lot easier with subsequent point releases.
Maybe our current app has unknown data race bugs, maybe not... with a crash free session percentage of 99.80% and hundreds of thousand monthly users, it's not something I consider a big enough problem, to the point where more friction to the language should be added to maybe fix it.
Is this shipping an entire copy of LLVM? What could possibly make this so large?
There are other (also large) downloads to enable producing static binaries on Linux, and those contain the required system libraries (musl libc, libicu, libcrypto, libpthread, etc.). Those are about twice as big as they need to be, because they bundle x86 and aarch64 together.
For example tests, there's so much magic. How do I know it runs the test for each item in the arguments array? What if there were multiple arguments? After using Go for close to a decade now, I'm really seeing the wisdom of avoiding magic, and making your testing code the same language as your building code! Compare:
Swift:
@Test("Continents mentioned in videos", arguments: [
"A Beach",
"By the Lake",
"Camping in the Woods"
])
func mentionedContinents(videoName: String) async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: videoName))
#expect(video.mentionedContinents.count <= 3)
}
Go: func TestMentionedContinents(t *testing.T) {
tests := []struct{ Name string }{
{"A Beach"},
{"By the Lake"},
{"Camping in the Woods"},
}
for _, tt := range tests {
video, err := library.FindVideoByName(tt.Name)
if err != nil {
t.Fatalf("failed to get video: %v", err)
}
if len(video.MentionedContinents) > 3 {
t.Errorf("video %q mentions more than 3 continents", tt.Name)
}
}
}
Go with timeout handling in case the FindVideo function takes too long (idk Swift magic well enough to know if it'd do this automatically!) func TestMentionedContinents(t *testing.T) {
tests := []struct{ Name string }{
{"A Beach"},
{"By the Lake"},
{"Camping in the Woods"},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
defer cancel()
video, err := library.FindVideoByName(ctx, tt.Name)
if err != nil {
t.Fatalf("failed to get video: %v", err)
}
if len(video.MentionedContinents) > 3 {
t.Errorf("video %q mentions more than 3 continents", tt.Name)
}
})
}
}At the risk of coming across a bit rudely: this feels analogous to asking “how do I know `for _, tt := range tests` loops over every element in the array?” Both are language/syntactic constructs you have to learn.
However, I do like Swift, I in fact single-handledy wrote an entire iPhone app used by 10s of thousands of people on it and there were a lot of wonderful things, like nullability being "solved", and smart enums etc. This isn't a language war, I like them both, and could point out flaws in either just as easily.
I just feel like Swift has a bit too low of a bar for adding new features, which leads to a lot of nice things, but also a lot of functionality bloat. I can look at Go written 10 years ago and it'll largely be the same as how it'd be written today; with Swift, it's night and day. I built the aforementioned app in 2014 to 2017 (mostly the first year), and there's so much I don't recognize.
I think one of the things that bothered me the most where I feel they sort of "jumped the shark" is with ViewBuilders. It looks like code, acts like it 99% of the time, but it isn't. Does that `var body: some View { ... }` return a view anywhere? No, and it'll break if you try! It's a whole different concept to admittedly offer a very nice experience emnulating React with idempotent views.
But still, it's awfully strange that this works:
struct IntroView: View {
@State private var text = "Yes"
var body: some View {
VStack {
Text(text)
Button("Toggle") {
text = text == "Yes" ? "No" : "Yes"
}
}
}
}
But this does not, because it's not _actually_ code. struct IntroView: View {
@State private var text = "Yes"
var body: some View {
VStack {
Text(text)
Button("Toggle") {
text = text == "Yes" ? "No" : "Yes"
}
print("debugging line")
}
}
}Now whether macros themselves were a good idea or not, that's an entirely different question.
But once your app hits a certain size, the abstraction will inevitably leak somewhere, and now you'll need to step back, and learn all about macros and viewbuilders to be able to fix your issues.
Probably worth it! They enable such a wonderful way of drawing a UI. But it's not a zero-cost abstraction, and I like that Go has eschewed _anything_ like that at all. There's no IEnumerable with yield return that compiles into a state matchine. No ViewBuilder that's a macro, not code. There's no Ruby-like adding methods at runtime (Model.find_by_bar_code???). It's all just plain code right in front of you.
They both have their strengths. I think Go made the right trade-off for simple high-throughput microservices, as they're trivial to understand and will never surprise you. And Swift made the right tradeoffs for a UI—-it would be painful to write it in Go.
My reaction as someone who used Swift extensively in 2015, quit, and now came back in 2024 is "wow, did they add too many features? that's a lot of quirks to learn." FWIW I don't feel the same way about TypeScript or C#.
It just feels at first glance like Swift let every feature in the door. And now we've got an awfully complex and laggy compiler in my experience, especially when you run into one of the type inferencing pitfalls leading to an exponential algorithm. Go explicitly made fast compilation a design goal and lost a lot of features to it.
Perhaps I'm just used to Python unit testing with similar decorators. Presumably, if you need to pass in two arguments, you'd either pass arguments: an array of tuples or a tuple of arrays for combinatorial testing.
I mean the APIs aren’t magic; you can “inspect macro” to see what code is generated at compile time which boils down to something similar to the Go code with better ergonomics.
Again, I really like Swift (besides xcode performance at times… ugh!). It's possible to find flaws in both languages yes still like them. Swift knocks Go out of the water in so many ways. But I'm scarred from ruby on rails magic growing and growing until you had to be an expert in the magic to write code, when the point was for the magic to make it easier.
The provided `drinkable` example I think is pretty bad and it's very surprising to me that this is a headline feature.
protocol Drinkable: ~Copyable {
consuming func use()
}
struct Coffee: Drinkable, ~Copyable { /\* ... */ }
struct Water: Drinkable { /* ... \*/ }
func drink(item: consuming some Drinkable & ~Copyable) {
item.use()
}
drink(item: Coffee())
drink(item: Water())
Here we have a drink() function that either accepts something `Copyable` OR non-Copyable (uh, I mean, `~Copyable`) and either consumes it...or doesn't? That seems to me like a fountain for logic errors if the function behaves completely differently with the same signature (which is, in fact, explicitly labeled `consuming`). It seems like it should not just compile if you try to call this with a `Copyable` type like Water, but it does.The syntax for representing this complex and weird concept of "maybe consuming" being `consuming some Drinkable & ~Copyable` is also just totally gross. Why are we using bitwise operation syntax for some weird and logically incoherent kludge? We cannot apply these & and ~ operators indiscriminately, and they do not mean the same thing that they logically mean in any other context, but this function definition definitely implies that they do.
With a generics definition like `some Drinkable` you are _restricting_ the set of suitable types (from any type to only the ones implementing `Drinkable`) which then _expands_ the available functionality (the method use() becomes available). From the perspective of type `Water`, it's conformance to `Drinkable` expands the functionality.
The language designers then get in a pickle if some functionality was assumed to exist for all types (e.g. `Copyable`)! By "conforming" to `~Copyable` you are _removing_ functionality. The type can NOT be copied which was assumed to be universally true before. Now, a generics definition like `some ~Copyable` actually _expands_ the set of suitable types (because Copyable types can be used as if they were non-copyable) and reduces the available functionality. It's the inverse of a regular protocol!
It becomes extra confusing if you combine `some Drinkable & ~Copyable` where `Drinkable` and `~Copyable` work in opposite directions.
This problem also exists in Rust. `Sized` is a trait (aka protocol) that basically all normal types implement, but you can opt-out by declaring `!Sized`. Then, if you actually want to include all types in your generics, you need to write `?Sized` (read: Maybe-Sized).
drink() takes a Drinkable. A Drinkables can be non-copyable.
Copyable is the default, so it has to mark itself as accepting non-copyables.
Coffee is non-copyable. Water doesn’t say which means it’s copyable (the default).
You can use a copyable anywhere you’d use a non-copyable since there is no restriction. So since drink can take non-copyables it can also use copyables.
I’m guessing the function definition has to list non-copyable otherwise it would only allow copyable drinks since the default is all variables are copyable.
“consuming some” means the function takes over the ownership of the non-copyable value. It’s no longer usable in the scope that calls drink().
For the copyable value I’m not sure but since they can be copied I could see that going either way.
On syntax:
Yeah it’s a bit weird, but there was a big debate about it. They wanted something easy to read and fast to use. NotCopyable<Drinkable> is really clear but typing it over and over would get real old.
~ is not special syntax. My understand is “~Copyable” is the name of the type. You can’t just put ~ in front of anything, like ~Drinkable. But since that’s the syntax used for bitwise negation it’s pretty guessable.
& is existing syntax for multiple type assertions. You can see the evolution in this Stack Overflow answer:
https://stackoverflow.com/a/24089278
Seems to read like C to me. It has to be Drinkable and not Copyable.
Like I said I haven’t gotten to use this yet, but it seems like a nice improvement. And I know it’s a step in the path towards making it easier to do safe asynchronous programming, object lifetimes, and other features.
> You can use a copyable anywhere you’d use a non-copyable since there is no restriction.
Effectively copyable always conforms to non-copyable, just not the other way around.
And the compiler effectively automatically notates literally everything with Copyable, so you need the explicit (& ~Copyable) in the function definition so you're still able to define functions within a ~Copyable protocol that have Copyable semantics.
It's very in the weeds, and I still don't like it (I would have preferred NotCopyable since the ~, especially next to the &, directly implies something like bitwise operators), but I guess custom ownership is itself very in the weeds and you will have to think about it hard no matter what approach is taken. I would have expected custom ownership to be fundamentally incompatible with Swift, but clearly it's here; I should probably read about it more so I have a more clear understanding.
(I also didn't realize & was extant syntax).
It took me a couple of minutes to figure out why it was in the function definition. I guess it had to be but that wasn’t obvious to me at all at first.
> And the compiler effectively automatically notates literally everything
Right. Just like how all classes in Java extend Object even though you don’t have to literally write it.
I believe they’re still working on a more Rust-like borrowing system, but I could be wrong. I know this helped them implement parts of the standard library much better because they could make assumptions that you can’t make with copyable objects.
I do get your point about just calling it NotCopyable. I don’t actually know why they settled on the name they did, I didn’t ever try to look that up. Maybe it’s because it’s a special thing that requires compiler machinery and there’s no way for you to make an equivalent?
Flutter, KMP and maybe even ReactNative will become much simpler and better choices and native on iOS will slowly but surely lose developers in my opinion.
At some point I hope it stabilizes without it being the end of the language.
I use Swift every day and while I still enjoy it, I think the language was nicer to use in its earlier iterations than it is now, which is a shame.
If I was Chris Lattner, I would be lying in my bed wide awake each night, staring at the ceiling, wondering how it went so wrong.
[1] See e.g. https://literatejava.com/exceptions/checked-exceptions-javas..., but also Java 8+ API's moving away from them.
In particular:
> Even with the introduction of typed throws into Swift, the existing (untyped) throws remains the better default error-handling mechanism for most Swift code. The section "When to use typed throws" describes the circumstances in which typed throws should be used.
https://github.com/swiftlang/swift-evolution/blob/main/propo...
To illustrate, Swift has a nice `rethrows` feature that helps with function composition.
If your function takes a function parameter that can throw, it can use 'rethrows' to say "I only throw if this parameter does". Then when passed a function that doesn't throw, your function need not be invoked with `try`.
This plays nicely with generics over throwing type, since the bounds propagate back to the function type. If the function parameter only throws a given type, that's what the function will throw.
Also helpful for reducing error boilerplate, `try` has the convenience form `try?` which means "just swallow the exception and return nil", and applies to a whole chain: `let f = try? this() ?? that() ?? "skip"` means f will be the result of either (throwing) function or a literal otherwise.
So something like
`func foo() throws -> Result`
Is actually
`func foo() -> Result | Error`
The compiler also forces you to handle any returned errors using `try`. So to call our example `foo`, you'd do:
`let result = try foo()`
You must either handle any throws error or include this call in an enclosing throwing function.
Implementation detail.
The two features are equivalent.
But the main difference is it's not hidden by the language; you have to 'try' any method that can return an error. Straight-line control flow in an imperative language is a good thing IMO. …but too bad about those defer statements.
I will repeat, whether exceptions are implemented as "nonlocal returns" (like setjmp/longjmp with stack unwinding) or as syntax sugar with sum return types, is completely irrelevant; an implementation detail. The generated machine code is different, but the behavior, the user experience is exactly the same.
>Otherwise you need all that "exception safe code" stuff.
In both cases, you need to write "exception safe code". Example of unsafe code in Java (a language that implements exceptions as non-local returns):
void bar() throws Exception { ... }
void foo() throws Exception {
mutex.lock();
bar();
mutex.unlock();
}
Example of unsafe code in Swift (a language that transforms errors into sum return types): func bar() throws { ... }
func foo() throws {
mutex.lock()
try bar()
mutex.unlock()
}
>But the main difference is it's not hidden by the language; you have to 'try' any method that can return an error. Straight-line control flow in an imperative language is a good thing IMO. …but too bad about those defer statements.Whether the language makes you prefix throwing calls with "try" is completely orthogonal to how they're implemented (nonlocal return vs sum return type). It's just a matter of syntax.
Compare to ATDs, where any generic function taking a T can also be used with Result<T> exactly the same. (Not a perfect comparison, but there are lots of other scenarios where ATDs just compose better)
Or they catch and wrap everything in some CustomSysException type so they only have to list one type and it’s not Exception, but then that’s really the same thing isn’t it?
I think it’s kind of a combination of not dealing with things and throwing them up the stack combined with too many exception types and maybe using exceptions when a result type would just be easier.
And in the end many errors can't be handled automatically, the best you can do is just show them to the user.
* - according to a couple of loudmouths on the internet
Also JVM guest languages like Kotlin and Scala treat all exceptions like runtime.
“throws Exception” on your method.
And then in main:
try {
// whatever
} catch (Exception e) {
System.out.println("Oops!");
}Forced checks on sum types with automatic unwinding are another way of doing checked exceptions, that apparently those haters love so much.
"0 matches".
Maybe on the next one.
I agree with most of the comments on this thread. The governance of the language really is a problem. You really can't run two models on the same project at once. The incentives just always get screwed up. Better to just be honest about what's going on, and then everybody can work together. But sometimes that means that people need to start getting paid, and companies love to not pay money if they can avoid it.
Also, Apple's tooling is just truly horrendous. It is just awful. I don't understand why they won't do anything about it. Xcode, there are some things that are nice about it, but compare it to really using a tool like IntelliJ IDEA well and Xcode just fails so hard.
And certain UX problems have literally persisted for a decade, and Apple has not done anything about it. It really is kind of unconscionable that Apple does this to their developers. I know on some level I am being ungrateful, because there are mountains being moved behind the scenes by Xcode, and I really do appreciate that. And from watching WWDC talks, I know that there are some really good engineers at Apple that are trying to do the right thing. And some of it is just corporate BS that is unavoidable. But they really do need to get better.
In any case, I hope that this update makes everybody's life better. When it is really nice, I think that Swift has some of the best developer ergonomics out there. That's just one person's opinion, of course.
I’d be interested to know how good this integration is in practice e.g. could it be used to integrate with Godot directly?
> SwiftGodot can be used to either build an extension that can be added to an existing Godot project, where your code is providing services to the game engine, or it can be used as an API with SwiftGodotKit which embeds Godot as an application that is driven directly from Swift.
Miguel de Icaza gave a great talk on Swift + Godot (among other things) https://www.youtube.com/watch?v=tzt36EGKEZo
I haven’t used it, but from looking at the long) video at https://youtu.be/ZQc9-seU-5k?si=JMFCWKUZ0vVtst9K (discussed in https://news.ycombinator.com/item?id=38444876), it looks pretty good.
There's lots of one-off uses for 128-bit integers scattered around systems programming (and programming in general, I just happen to come from more of a systems background). Individually using a pair of 64b integers for each case is totally workable, but eventually life is easier if you just have an Int128 type. There's not really any one use case that drove their inclusion in the standard library, rather lots of times that we were saying "it would be kind of nice if we had this".
Makes it a lot easier to handle packed binary files etc.
Mostly that's minimizable with good practice, but can be annoying to those used to hierarchical namespacing and static memory reclaimed after the class becomes unreachable.
Of course, taking them away doesn't stop them from other kinds of over-organization like ArrayFactoryFactoryStrategies, but it helps a little.
Makes me appreciate how even Node.js got it right so early on.