14 ms·
Tell HN: Rust Is Complex
I'll preface this by saying I like Rust, and I've found myself coding more in Rust the last two years than anything else. But Rustaceans kind of like to laugh at Go, because it's not as expressive or elegant a language by comparison. That's mostly true, think of how nicely Option types, enums, and iterators work in Rust compared to Go. However, Go is simple and deeply pragmatic. There is an underrated value in that. Some parts of Rust are starting to remind me of the horror I ran from with C++. Look at this:
Update: It is possible to abuse existing CoerceUnsized implementations on stable. See #85099 (although I created that issue before reading any of this issue and its IRLO thread, so don’t expect any syntactic similarity to the unsoundness examples of this issue).
The type Pin<&LocalType> implements Deref<Target = LocalType> but it doesn’t implement DerefMut. The types Pin and & are #[fundamental] so that an impl DerefMut for Pin<&LocalType>> is possible. You can use LocalType == SomeLocalStruct or LocalType == dyn LocalTrait and you can coerce Pin<Pin<&SomeLocalStruct>> into Pin<Pin<&dyn LocalTrait>>. (Indeed, two layers of Pin!!) This allows creating a pair of “smart pointers that implement CoerceUnsized but have strange behavior” on stable (Pin<&SomeLocalStruct> and Pin<&dyn LocalTrait> become the smart pointers with “strange behavior” and they already implement CoerceUnsized).
More concretely: Since Pin<&dyn LocalTrait>: Deref<dyn LocalTrait>, a “strange behavior” DerefMut implementation of Pin<&dyn LocalTrait> can be used to dereference an underlying Pin<&SomeLocalStruct> into, effectively, a target type (wrapped in the trait object) that’s different from SomeLocalStruct. The struct SomeLocalStruct might always be Unpin while the different type behind the &mut dyn LocalTrait returned by DerefMut can be !Unpin. Having SomeLocalStruct: Unpin allows for easy creation of the Pin<Pin<&SomeLocalStruct>> which coerces into Pin<Pin<&dyn LocalTrait>> even though Pin<&dyn LocalTrait>::Target: !Unpin (and even the actual Target type inside of the trait object being returned by the DerefMut can be !Unpin).
Methods on LocalTrait can be used both to make the DerefMut implementation possible and to convert the Pin<&mut dyn LocalTrait> (from a Pin::as_mut call on &mut Pin<Pin<&dyn LocalTrait>>) back into a pinned mutable referene to the concrete “type behind the &mut dyn LocalTrait returned by DerefMut”.
From: https://github.com/rust-lang/rust/issues/68015#issuecomment-835786438
Where's that elegance now? I still maintain that I'd rather use Go when the problem domain allows for it (e.g. can use a garbage collector, don't need fast interoperability with C, don't need maximum performance.)
- RcouF1uZ4gsC 4y agoQuestion: How much of Rust’s type system complexity is due to async? It seems Pin is mostly related to async and the official examples for GAT are for async. Given the complexities of async and how other alternatives such a Java Loom’s virtual threads, in about 10 years I am not sure that we won’t see Rust going all in on async as a mistake.
- 2fast4you 4y agoSince we’re comparing Go and Rust, I think Go really nailed it with goroutines and channels. It’s much easier to get your head around than async.
- deleted 4y ago[deleted]
- afdbcreid 4y agoI don't think this is right. To me at least JS's promises and C#'s async/await are as easy as Go's goroutines. The problem with Rust is the effort to make everything static and without heap allocations, which is understandable but enforce really hard-to-grasp design. On the other hand, you can get rid of most issues of async even in Rust by boxing.
- toast0 4y ago> I am not sure that we won’t see Rust going all in on async as a mistake. I'm about a month into Rust, but Rust didn't go all in to async; it only went halfway in, which is why some functions are async and others aren't. Erlang is all in on async/green threads/tasks. From what I gather, so is Loom. There's no special way to write async code, you just write regular code and it works; for Loom you just spawn your threads a little differently. For Rust, you have to be somewhat careful about what you do or don't do in a task, and you have to write .await everywhere.
- sph 4y agoErlang isn't async. The actor model is great because you only have to think in single threaded mode. And with immutability being a core language feature, data races and other concurrency issues are literally impossible.