I'm certain that the design has been carefully considered at each stage. I'm saying that in doing this incrementally by adding “small” features—each of which has arguably only a small complexity cost—the massive opportunity provided by existing features has been overlooked. (This is not the first such failure, IMO).
Yes, in fact that is an inherent part of the “shape” of computations implied by the Law of Exclusivity. It is this shape that I believe has not been carefully considered. References in Rust (and borrows/ inouts/~Escaping in Swift, or similar constructs in any language that statically upholds the LoE) are not generalized references at all, because of these limitations:
- The LoE is upheld by the compiler (a good thing!) which means a lot of code that would respect the LoE if analyzed dynamically has to be expressed differently.
- Even if they seem to escape they are local to something up the call chain and the same code could have been expressed without escaping by using higher-order functions just as yielding accessors can.
- You may be able to store them temporarily (because of the previous bullet) but you can never store (or even get) multiple mutable references into the same whole object without some unsafe code that exposes multiple non-overlapping parts of a single whole at once. That severely limits what you can express with them, and it's not clear what compelling use-cases remain that justify the complexity.
Yielding subscripts cover only a subset of Rust’s ownership capabilities, not anywhere near 99.9%.
I didn't make any claim about a percentage of Rust's ownership capabilities. It is a claim about use-cases. In fact, you're proving my point: most of these other capabilities account for a lot of complexity and language weight and are only “needed” in a small fraction of use cases.
There also borrow bindings
Which I mentioned as a missing piece for Swift (which IMO should have been added before any of this other stuff because synergy with yielding accessors would have helped reveal that more complexity isn't needed), but which can also be expressed as parameters to higher-order functions.
stored references
As noted, it's really unclear how important or useful this feature is. It is the same capability as the aforementioned “remote parts” which Hylo is currently refraining from surfacing because we're not sure it's a capability/complexity win, but notably we believe would still be much less complex than first-class references.
borrowing iterations
Easily expressed using yielding accessors.
repeated access without recomputing the projection
That's just borrow bindings again, is it not?
generics
I don't know what you have in mind here, but the way lifetime annotations creep into the type system via generics is among the worst complexity effects they've had on Rust.
yielding accessors combined with the recently pitched
BorrowandInouttypes seem capable of expressing a large fraction of commonly written Rust borrowing code.
My point exactly ![]()
![]()
! Swift could avoid a huge amount of complexity by starting there. (Though the idea that there would be first class types rather than binding modifiers is extremely worrying. Where is this pitch?)
As for complexity being hidden:
- It is a fallacy that complexity in the fundamental language base is ever really hidden. It's always there for anyone who wants to really understand what's going on. That is even true for features such as Builders that sit on top, but at least there's a level of the language you can fully understand without confronting them.
- This is an effect of the incremental adoption and rollout. It only seems hidden because the standard library hasn't (fully?) adopted it, at which point it is going to be in everyone's faces. It will also become an imperative for library developers to understand and support these features.
For those who do need it, the complexity comes from the real world no matter what language you use: Swift, Rust, or C++.
That's an oversimplification. There are tradeoffs here. The vast majority of these problems are easily solved by a safe language with a few well-chosen features. There's a small corner of problems that Rust can solve safely, but only at a huge cost in language complexity, and in complexity of the code that solves those problems. It is much harder to write and verify the correctness of that code than of the same code written in an unsafe language, because of all the hoops you have to jump through. Not everything is best expressed in the type system, and the cases that aren't expressible with yielding accessors and two new bindings are so rare as to not be worth supporting in safe code.
It's not entirely true that the compiler is the guarantor of safety. The standard library plays a huge role. There are some things that can't be implemented in safe code (like Array) so the standard library implements them as safe abstractions. Because the unsafe code is localized, validating the correctness of these abstractions isn't hard. The same facilities are available to end users for good reason. Did anybody research the real use cases in this corner and consider solving them that way vs the complexity costs of these new features?
I bet they didn't, but if I'm wrong, someone should be able to provide examples that fit the shape described above and become much better and cleaner with all these new language features than they would be without them. The fact that we in Hylo land have been having this same conversation with Rust folks and haven't gotten a single concrete example makes us strongly suspect the same is true for Swift. I wouldn't have posted this otherwise.