Every time I go to implement Deref for MyType, I feel as though it would be straightforward to have the compiler implement DerefMut for the exclusively borrowed version of MyType.
The borrow and exclusive borrow types are different types. We don't want the borrow type to implement DerefMut (clearly). The question is: when I implement Deref for MyType, why is there not a built-in way to have the compiler infer the exclusive borrow version of MyType (&mut MyType), then implement DerefMut for that version of the type... using what I have specified in the Deref implementation?... Or does it already? (apologies for the confusing question on my part)
Not every type that is Deref should be DerefMut. A whole bunch of multi-threaded/concurrent data structures shouldn't, for example. This is the reason why Deref and DerefMut are two separate traits in the first place.
Also, implementing a trait based on the implementation details (i.e., function body) of another trait would violate encapsulation. This is undesirable for the same reason as inferring function signatures from their bodies is not performed.
“Your scientists were so preoccupied with whether they could, they didn’t stop to think if they should.”
Because implementing DerefMut too is often just wrong.
Take this type, for example:
pub struct Even(u32);
It'd be completely fine for it to be Deref<Target = u32>. But if it was DerefMut<Target = u32>, then I could do *foo = 1; and violate the "it's always even" invariant.
So it's very important that they're different types.
(And it's really not that big a deal to implement DerefMut -- you don't even need to specify the Target again, since it uses the same type from Deref. You could of course also make a macro or a derive if you need to do this a bunch.)
I agree that it's not too much trouble. I wanted to understand the motivation. The Even example is a good one.
The multi-thread example seems less convincing. But I'm intrigued by the
implementing a trait based on the implementation details
@H2CO3 How is that different from deriving Into from the From implementation? (here we are inferring one trait based on another trait for the same type, so is different to my example in that way)
How about the other way around? - in other words, I implement DerefMut, why not have Rust infer the implementation for Deref?
The Into blanket impl is a regular impl that doesn't need to know anything about the implementation details of its corresponding From::from() method. It literally just calls From::from(). It's not like the compiler magically reads and understands (and transforms) the body of From::from() to generate an equivalent into() – it simply, mechanistically delegates.
You can't do the same with Deref. There's no way you can go to a &mut T from a &T. The compiler would have to actively look at and understand/substitute the body of the Deref impl to generate DerefMut.
It's got all the same problems. You can't go from &mut T to &T simply by delegating, because then all immutable accesses would need mutable borrows (and this would in turn lock "immutably" borrowed values as if they were mutably borowed, forbidding simultaneous immutable derefs).
Arc supports Deref, but would be unsound if it supported DerefMut. One of the key features of Rust, one of the reasons the language exists, is preventing data races like that.
Thank you for clarifying that for me. That resonates loud and clear.
I was thinking that if I only wanted to borrow I would implement Deref, if I wanted both, I could do each manually, or implement DerefMut. So, by definition, you implement an exclusive borrow, you get the borrow "for free".
The multi-threaded example is less convincing because it's not like users of types in a multi-threaded setting don't always have to consider the design differently when dealing with a mutable version. The user has full control as to when to use the exclusive borrow. In the event you want to keep some subset of the type immutable I suspect we have moved away from a place where Deref makes sense?
Would that not be prevented by definition of the different types involved? (borrow and exclusive borrow are different types). For now I'm only thinking at the level of monomorphization.
I don't understand what you mean. My point is that implementing DerefMut alone doesn't work, because it always mutably borrows. The signature of DerefMut::deref_mut() is (&mut Self) -> &mut Self::Target. This requires mutably borrowing self, period. It doesn't allow only immutably borrowing, which is a problem, because shared derefs of the same object at the same time would then be impossible.
No, Arc can be cloned, which means it may have many exclusively-owned copies. Anything owned can be borrowed mutably. &mut Arc<T> can exist and is safe, as long as &mut T can't be accessed.
... when I said "for free", I meant in the scenario where the compiler would use my implementation of DerefMut to implement Deref. My thinking falls apart with what you, @H2CO3, said earlier about how Rust generates default implementations from definitions of other traits. In my idea, I imagined this could be solved by automatically parsing &mut self to &self to translate my spec for DerefMut to the Deref during monomorphization.
This was motivated by the fact that all I'm doing with Deref is pointing Rust to my "inner value"... wherever that might be. To where I point does not change for DerefMut. I updated the auto implementation idea so it would only happen if I implemented DerefMut. The "savings" would only be that I don't have to type out the boilerplate for Deref. This option would make it clear that there isn't anything to think about once I've decide to implement DerefMut, Deref is rote.
Yes, it definitely could be done to some extent at least.
However, it shouldn't be done. Precisely because it would require peeking into the implementation of a method.
But this isn't true in general. Deref and DerefMut are allowed to invoke code of arbitrary complexity. While that's definitely ill-advised to do, it's also definitely possible – and it should be, because Deref and DerefMut are just traits like any other, except that they can be invoked via the dereferencing operator, which is basically just syntax sugar.
I'll have to consider this point further. My limited understanding was that this rule was more so the compiler knew how to "type" a function: take the signature or the body?... design choice: signature is the source of truth, generate errors accordingly. Without this resolution, the job of type inference would be if not exponentially complex, impossible(?). How you are positioning the rule suggests there is more to it that I'll keep an eye out for.
My intuition is that the default behavior I've described would work the majority of the time while signaling to the programmer "don't think about it, unless you have reason to", kind of thing. The user is always free to overwrite the default.
You keep bringing up technical limitations/points, so perhaps I wasn't clear. The technical possibility is not the point.
First off, the above statement of yours isn't even true. Haskell does fast global type inference (generics included) just fine, it's absolutely possible and practical.
Maybe some currently implicit coercions would need type annotations.
Now, to the point: the reason why it was decided that function signatures should always be explicitly typed is that it's easier to read for the programmer, and it's more robust to changes in the implementation. It's not a technical necessity. But if types were inferred from function bodies, then there would be no (or insufficient) type information at API boundaries, and interface types could unexpectedly change due to changes in the implementation.
That in turn could result in consequences ranging anywhere between mildly annoying ("I have to look up the type separately in the docs") and catastrophic ("this unsafe code assumed type X but it unexpectedly changed to type Y and now you have UB").
Rewriting Deref to DerefMut would suffer from the same kind of problem. It's not that it would not be doable in some limited/simpler cases at least. Rather, the problem is that by doing it, one trait's guts would remotely influence the syntax of the guts of another trait. That's counter-intuitive and pretty hard to debug if it ever goes wrong.
Good point. I'm convinced (the use of the word "guts" was perfect :))
Given that in Rust functions have to have a type signature... and a body, we have two sources of truth. If the two don't match, we have an error. Which one to use to cascade inferences, clearly the fn signature makes sense for the reasons you describe.
I'm not sure I understand how changing an implementation can introduce unexpected behavior unless the implementation also requires a change in the fn signature to compile, which is the case today... and makes sense. In other words, any changes in implementation that matter (X to Y, UB), will force an update to the fn's type signature thus signaling accordingly (broken code, but not UB because the changes never compiled). Right?
I suspect this is not the case for closures where type annotations are optional and derived from the body.
Yes, this is the case today, and I was arguing that this behavior is desired. I'm not arguing that this kind of misbehavior is possible with today's Rust. It's exactly my point that Rust should not be changed in a direction that would allow the described issues to happen.
A trivial counterexample with global inference would be:
// inferred as: for<'val> fn(&'val u64) -> &'static u64
fn get_static_ref(value: &u64) -> _ {
&42_u64
}
// Not robust!
fn main() {
let raw_ptr = {
let short_lived: u64 = 1337;
get_static_ref(&short_lived) as *const u64
};
// SAFETY: raw_ptr comes from a &'static u64
let static_ref: &'static u64 = unsafe { &*raw_ptr };
dbg!(static_ref);
}
The above code is currently sound. It gets a reference to a static, converts it to a raw pointer, and converts the raw pointer back to a 'static reference.
However, if you were to change get_static_ref() so that it returned its short-living argument instead, then the code would cause UB and there would be no compiler error. The compiler would happily infer the return type to be &'val u64 instead of &'static u64, and you would convert the returned, now dangling, raw pointer back to a 'static reference.
With the status quo, this isn't possible, because you have to fully annotate the function signature. If you start with -> &'static u64, and you try to return the reference passed in, then the compiler will yell at you instead of silently changing a lifetime that the unsafe code relied on.
I am surprised that nowhere in this conversation was it brought up that a macro would be a fine way to implement this kind of syntactic rewrite, and with an explicit macro attribute on either the type or the Deref impl I think this completely solves the concerns re: opt-in for deliberateness. I would love to just slap a derive(DerefMut) on the type and get this implemented automatically. (You could try to do derive(Deref) as well but you would need some syntax for writing the appropriate projection and the type.)
Problem is, macros can't inspect anything other then their direct input. That is, if you derive(DerefMut) on a type, it would know nothing of the manually-implemented Deref.