5 ms·
I love Rust, but one thing that seems like a glaring design flaw is that adding a new trait implementation can change the behavior of existing code. This is ver
by ebingdom 5y ago
I love Rust, but one thing that seems like a glaring design flaw is that adding a new trait implementation can change the behavior of existing code. This is very counterintuitive, because it seems like a purely additive change. In my opinion, the convenience of auto-dereferencing "as much as possible" to make method call syntax magically work through the indirection of references is not worth the lack of stability guarantees that it causes. This isn't a normal kind of breakage where the compiler points out what code you need to fix; this is a much worse kind of breakage where you might not even realize your code now behaves differently. In my opinion, the notion of "editions" is not an acceptable solution to this.
For a language that prioritizes safety, there are a surprising number of gotchas in Rust (another example is the large number of partial functions in the standard library).
- Arnavion 5y agoA lot of `expr.expr` would need to become `(&expr).expr` or `(&mut expr).expr` if auto-dereferencing wasn't a thing. C++ has `.` and `->` but Rust only has `.` (To be clear, what's happening with `array.into_iter()` is unsize coercion, not auto-dereferencing. `[T; N]` does not impl `Deref<Target = [T]>`. But the point is the same.)
- ebingdom 5y agoI think you have it reversed. `expr.expr` would need to become `(*expr).expr` if we didn't have auto-dereferencing.
- Arnavion 5y agostruct S; impl S { fn foo(&self) { } } let s = S; s.foo(); That compiles today. Your proposal would require it to be `(&s).foo()`
- pcwalton 5y agoIn other words, there is auto-ref in addition to auto-deref, and auto-ref causes the same issues of possibly-surprising method resolution. As I mention above, auto-deref is just one way that methods could resolve to the "wrong" implementation, and there would be no solution that I can see other than to remove dot entirely, which isn't viable.
- a1369209993 5y agoNo, IIUC, the sematics is (approximately): a.foo(b,c); vvvv typeof(a)::foo (??? a,b,c); vvvv A::foo /* takes &A */ (/*so this is:*/& a,b,c); vvvv A::foo(&a,b,c); Auto-derefencing is: a.foo(b,c) vvvv // a is &X, so replace it with (*a) (*a).foo(b,c) // wrong; should be a.foo(b,c); we called vvvv // a method on the pointer, not what it points to X::foo(???(*a),b,c) // should be A::foo(???a,b,c) // continue as above Auto-derefencing changes which type the method is looked up relative to, not just how the object is passed to it.
- Arnavion 5y agoYes yes, there's auto-ref, auto-deref, deref coercion, unsize coercion, ... All of them are forms of "`lhs.rhs` tries to look up a different type to apply the `rhs` to vs what type `lhs` actually has," or more generally "I wrote an expr of type T but the compiler treats it as an expr of type U for :reasons:" I called it "auto-dereferencing" because that's what OP used when talking about `[].into_iter()`, and then clarified that it's actually an unsize coercion, not auto-deref.
- a1369209993 5y ago> All of them are forms of "`lhs.rhs` tries to look up a different type to apply the `rhs` to vs what type `lhs` actually has," Nope, it uses the same type (A), just blindly adds a operator to the argument based on which method is called. You could just as easily have `a.foo()` -> `A::foo(++a)` instead; it's just[0] that adding `++` would be largely useless. 0: You'd also need a explict annotation on the declaration of foo, but that's a convenience issue.
- jeltz 5y agoWhat if borrow and deref were postfix operators? E.g expr&.expr? That way it would be explicit without parenthesis soup.
- steveklabnik 5y agoPeople already complain rust is too punctuation heavy; I can already read the “this is Perl!” comments on this very website. EDIT: hilariously exactly this happened three minutes after my post…
- steveklabnik 5y agoStatic typing helps significantly here, though obviously not completely. Often, the failure mode is a compiler error, not silent code changes. Note the example of TryFrom; the failure mode isn't that your program is now running a different implementation, but that, because there are now two possible implementations, it becomes ambiguous which is selected and a compile-time error results. Same with the array change, the failure mode was breakage. While in this case, the method's names, arguments, and argument types are identical, the return type is different, which means other code expecting a certain type now has a different type. For that to silently change behavior, it would also need to match up on everything on the return type as well.
- pcwalton 5y ago> I love Rust, but one thing that seems like a glaring design flaw is that adding a new trait implementation can change the behavior of existing code. This is very counterintuitive, because it seems like a purely additive change. > In my opinion, the convenience of auto-dereferencing "as much as possible" to make method call syntax magically work through the indirection of references is not worth the lack of stability guarantees that it causes. Auto-deref is just one way of many that the dot operator can resolve to an unintended method. This problem would still exist even if auto-deref were removed. What you're really objecting to is the existence of the dot operator at all. I think the dot operator pulls its weight, given how often it's used: I encourage you to check out Simon Peyton-Jones' thoughts on "the power of the dot" [1]. [1]: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/ECOOP-July09.pdf https://www.microsoft.com/en-us/research/wp-content/uploads/...
- ebingdom 5y agoI'm pretty confident Simon Peyton-Jones would never find it acceptable that adding a new type class instance changes the behavior of existing code, regardless of what convenience it brings. Yes, the dot operator is useful (as SPJ acknowledges in that talk), but convenience at the cost of sacrificing the ability to reason about code and how its meaning evolves is not worth it to the typical Haskeller. SPJ isn't talking about Rust specifically in that talk; he's only talking about the convenience of IDE integration and namespacing based on the receiver.
- lenkite 5y agoYes this is a glaring safety issue and should ideally be an explicit opt-in feature.