I can’t speak for the author, but I’ve had a similar feeling. Where (safe) Rust’s core principle is memory safety at all costs, I feel that Swift takes a more nuanced approach, prioritizing making good programming patterns easier while still being memory safe (though perhaps not as expressive as Rust).
Swift also is much more principled when it come to certain things like exceptions. Whereas Rust (if I remember correctly) allows arbitrary panic-ing and catching of panics, Swift forces you to acknowledge a possible panic at function call sites using “try”. Without “try” control flow can’t suddenly end.
> Whereas Rust (if I remember correctly) allows arbitrary panic-ing and catching of panics
While it is possible to "catch" a panic in Rust, this is not an error-handling strategy, it is a correctness fallback. The intent is that, if you're writing a Rust library that exposes a C ABI, you halt the panic at the boundary of your library, because there is no standardized cross-language unwinding scheme and so unwinding across an FFI boundary would be UB. It's not there for error handling (and it can't be, not only because the API is deliberately designed to discourage it, but also because Rust programs can be compiled in a mode where panics abort instead of unwind).
> Swift forces you to acknowledge a possible panic at function call sites using “try”. Without “try” control flow can’t suddenly end.
This is a bit inaccurate. You can absolutely do things like divide by zero in a Swift function that isn't marked as `try`, which will crash the program. This is the same behavior that is expected of panics in Rust.
> while still being memory safe (though perhaps not as expressive as Rust).
Rust's memory safety protects against data races. AFAIK, Swift does not.
> Swift also is much more principled when it come to certain things like exceptions. Whereas Rust (if I remember correctly) allows arbitrary panic-ing and catching of panics, Swift forces you to acknowledge a possible panic at function call sites using “try”. Without “try” control flow can’t suddenly end.
Generally in Rust, a panic means the program has entered a state that is unexpected by the programmer. One example is an unexpected index out of bounds error.
For cases where an error is an expected program state, like a web request or input validation, it's recommended to use `Option` or `Result` instead. Rust will force you to handle the error case.
Yes, any function can panic, and any caller can catch panics, but panics are not the primary "exception" analogue in Rust. Panics are only for fatal errors.
I don't know anything about Swift, but from a quick search I see something called "Fatal Errors", which I assume act a similar way.
Now to address the points in your gist.
> lauded the Borrow Checker for discouraging people from using the borrow checker! This results in less performant code much of the time.
The borrow checker is much improved with non-lexical lifetimes which makes it far easier to deal with.
> The escape hatch of macros and build-time source generation has allowed Rust to sweep some of its usability issues under the rug (Just use a macro!). Swift strives to support all of the use cases developer have in the language itself
I have no idea how you could, for instance, implement something like `serde` without macros or reflection without building it into the language. Other common uses, like `lazy_static`, have been replaced with better APIs that don't use macros at all. Building these things into the language or standard library without enough experimentation beforehand can be problematic, as `lazy_static` shows.
Rust's development is more open than Swift. It doesn't have the luxury of a single owner that can dictate THE WAY to do things, because it must support use cases from web servers to embedded MCUs. Macros and other forms of build-time execution provide that flexibility.
> Coarse Imports
Personally, I think Rust's module system and imports area the best I've ever used. As I said, I've never used Swift. Are all of the standard library functions in a single namespace?
I imagine that this makes implementing things like refactoring much easier.
> First party IDE
>
> Say what you want about Xcode
Xcode is MacOS exclusive.
> With the current state of RLS, developers often forsake modern IDE features even though reliable code completion, refactoring tools and diagnostics greatly benefit developers.
RLS is now deprecated, and rust-analyzer is fantastic.
> Out-of-the-box debugging experience
I haven't ever tried interactive debugging with Rust, so I can't comment.
> One of the more powerful uses of a type system is to have functions perform different processing based on the type of its arguments.
I think one of the fundamental difference of today is that this should be handled by traits, not by standalone functions.
> In Rust, this is achived via the From and Into mechanic. The first unfortunate consequence of this is that you often have to create a new type and trait pair per argument you want to be polymorphic.
I don't think this is a common pattern at all. Generally you'd just implement the trait for each type and put the different behaviors in the trait impl, rather than in the function using an enum.
> you can effectively only ever have one generic implementation of your custom into Into… for a trait, since otherwise the type checker complains there can at some point in the future be a collison.
This is only true for types outside your crate.
> In Rust, you cannot create trait objects out of certain types of traits. Often, you could use trait objects for this type of thing, but I haven't figured out a way to do this with more complex constraints. For instance:
trait Bar {}
trait Baz {}
trait Foo {
// This is invalid
fn foo(t: &(Bar + Baz)) -> ();
}
I think what you're looking for is:
trait Foo: Bar + Baz {
fn foo(&self);
}
This will work as long as Bar and Baz are object-safe.
> Making it generic disallows using Foo as a trait object:
trait Foo {
fn foo<T: Bar + Baz>(t: T) -> ();
}
// This is invalid
fn do_work(f: &Foo) -> () { … }
You can make the trait generic instead of the function:
Swift also is much more principled when it come to certain things like exceptions. Whereas Rust (if I remember correctly) allows arbitrary panic-ing and catching of panics, Swift forces you to acknowledge a possible panic at function call sites using “try”. Without “try” control flow can’t suddenly end.
Admittedly, I haven’t used Rust significantly in a couple years, but I wrote this a few years back and some of the points still have merit: https://gist.github.com/GeorgeLyon/c7b07923f7a800674bc9745ae...