A design problem with references vs lifetimed types

I rant for a bit
S
Composite
CompositeMut
A
B
C
TA
TB
TC
TAB
TAC
TBC
T0
T1
T2
R
T

These are all the names of types and traits you have used in your examples. Not a single one is a concrete noun that describes what the thing does or is.

This is not real code. This is an exploration of the language, which is all right in its own way, but you can't solve architectural problems by throwing toy examples at them because the architecture of the solution depends on the nature of the problem by definition.

Given any piece of code stripped of context, you can keep saying "but what if X, Y and Z?" until there are no possible solutions left. But that's not a productive line of questioning. I see the same thing happening with people who think Rust needs inheritance: they construct a toy problem such that the only possible solution is exactly inheritance, then say "How do you solve this in Rust?" And people in threads like this give some helpful suggestions for reframing the problem, or transform the code in such a way that it technically works but isn't very pretty, and the asker gets frustrated and says "Why can't Rust do this one simple thing?" when the reality is that the problem they're imagining doesn't exist in real life. People solve vastly complicated problems in Rust without needing inheritance. (Serde is a great example.)

I'm not saying you've completely imagined your problem. What I am saying is that even if you have a real problem, it's not evidenced in the alphabet soup of toy examples in the thread so far. If you have real world code that you think isn't well designed because you can't abstract over mutability, post that and we'll see if there's a way to organize it better.

I will agree that the inability to abstract over mutability is occasionally a pain point. However, in all the cases I am currently thinking of, the commonality between the mut and non-mut versions is purely on the syntactic level. Abstracting common syntax, when the types are not held in common, is just what declarative macros are great for. Maybe this is the kind of problem that actually should just be solved with a macro.

1 Like

That is a fair point, however, as @kornel said, abstracting over mutability is not quite possible in Rust, due to them (&T and &mut T) being very different beasts from the borrow checker's PoV.

In the case of BTreeMap and most other collections, I believe the iterator duplication problem could plausibly be solved because they are already using raw pointers and unsafe all around to begin with. So I could imagine that it's only a matter of re-using one common implementation and casting between *const T and *mut T (or vice versa) after having ensured the required invariants.

(I'm not sure if this is actually how it is done, though.)

1 Like

I'll just re-state that example, since it seems to have been overlooked: there can be such a gigantic semantic gap between the looser pre-condition and looser post-condition of a shared input → shared output w.r.t. exclusive input → exclusive output, that trying to merge both implementations as one will be over-restrictive at best (if people start from the exclusive case, and then s/&mut/&/g-"copy-paste" the implementation to the shared case), and error/UB-prone at worst (if people start the other way around).

So even if cumbersome, Rust has made the right choice, here.

  • For very trivial cases, such as field accessors, macros are the go-to tool to reduce the boilerplate.
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.