> 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:
trait Foo<T: Bar + Baz> {
fn foo(&self, t: T);
}
> Multiple if let cases
They are a work in progress, available in nightly: https://github.com/rust-lang/rust/issues/53667
But they do require an extra level of parens in some cases, though I think that's insignificant.
> Guard Statements
Rust has these now as "let ... else" statements.
let Pattern(binding) = thing else { ... };
> Explicit try
This was covered above: use Result or Option instead. Panics are not exceptions.
And as for unwrap, yeah you just have to know that it will panic. It's just a thing you have to know about the language, like `try`.
> Rethrows
Transpose is stable now. But generally for that case I'd do this instead:
let y = Some(1).ok_or(Error)?.and_then(|i| {
if i > 3 {
Err(Error)
} else {
Ok(i + 1)
}
})?;
> Ternary Operator
I prefer the lack of a ternary, since, as you show, an if can do the same thing.
> String Interpolation
You can include variable names inline now. And they're not C-style at all.
> Named arguments
Personally, I've never felt the lack. Especially with the inline hints from rust-analyzer.
> Default arguments
I actually prefer how Rust does not have function overloading. This includes default arguments and optional arguments.
> Function-level polymorphism
And the above also applies to this.
> This is also useful for operators (in Swift, + is a polymorphic function)
This is implemented with traits in Rust.
> Alternatively, you could use use:
use MyDescriptiveEnum as E;
match e {
E::Foo => …,
E::Bar => …,
}
You can do a glob import instead
use MyDescriptiveEnum::*;
match e {
Foo => …,
Bar => …,
}
> Raw types for enumerations
Rust enums have this for primitive numeric types, but others do not.
> Rust's enum and struct initializers all use different syntax. In Swift, there is a single syntax
Sure, they both use parens, but they are still different syntaxes.
> Trailing Closures
I don't like this. Seems like a special case for little to no reason.
> Default Closure Arguments
Same as the above.
> Extensions, Type Inference for Associated Types
I agree these could be useful improvements.