-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix incorrect fmt::Pointer
implementations
#328
Conversation
@tyranron are you still planinng on addressing this? |
@tyranron ping |
@JelteF yes, sorry for not being responsive. I was a little bit overwhelmed on my job last 2 months. I'll try to finish this in next 2 weeks or so. |
No worries, it happens. "Next 2 weeks or so" sounds amazing. |
Oh... crap. This is a bit of a rabbit hole. I've figured out the solution for cases like We basically hit the difference between accessing a @JelteF I think that supporting |
@JelteF thinking about it more, I'm unsure whether we should fix Instead, I propose to leave desugaring as it is now, and:
This way, throwing the compile-time error, we bring the library user's attention to this edge case, so he will much more aware about the implications for the |
tl;dr I like your previous proposal much better. Honestly, I cannot think of a reasonable case where anyone would do something like this: I think it makes sense to document the weirdness around this, but I think we should support |
@JelteF by the way, despite me being too busy to get on this lately, it appears for the better... today, when I was dreaming, I figured out the solution I do like and that should work in all the cases: // We declare in `derive_more` additional types machinery:
#[repr(transparent)]
pub struct PtrWrapper(T);
// We do the necessecarry dereference in the `fmt::Pointer` impl for our `PtrWrapper`
impl<T: fmt::Pointer> fmt::Pointer for PtrWrapper(&T) {
fn fmt(&self, f: fmt::Writer<'_>) -> fmt::Result {
(*self.0).fmt(f)
}
}
// For any other `fmt` trait we just do the transparent implementation.
impl<T: fmt::Debug> fmt::Debug for PtrWrapper(T) {
fn fmt(&self, f: fmt::Writer<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
// And also `Deref`+`DerefMut`.
// And in the macro expansion we do this:
let _0 = ::derive_more::fmt::PtrWrapper(&self.0);
// instead of this:
let _0 = &self.0;
// so here, wherever the `_0` value is engaged, it will be dereferenced once used as `{:p}`
write!(f, "--> {_0:p}")
// and any other cases should work just fine due to transparent impls of `fmt` trait and `Deref`/`DerefMut`. |
@tyranron that sounds like a great solution. And I think you wouldn't even need to implement |
Okay I tried this out just now, and it has an unsolvable problem afaict. By wrapping the reference in So I think the only reasonable solution is to special-case |
For me, this situation seems to be less confusing than special-casing, because auto-deref should work for the majority of cases, while the special-casing breaks whenever there is any expression. Also, wrapper type doesn't allow the code to be incorrect in a subtle way, like the special-casing does. |
I think a fairly common case that would be much more difficult is doing some arithmetic with one of the arguments: use ::core;
use core::fmt::{Debug, Formatter, Result, Write, Pointer};
use core::prelude::v1::*;
#[repr(transparent)]
pub struct PtrWrapper<T> (pub T);
impl<T: Pointer> Pointer for PtrWrapper<&T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
(*self.0).fmt(f)
}
}
impl<T> ::core::ops::Deref for PtrWrapper<&T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
fn main() {
let a : i32 = 123;
let a = PtrWrapper(&a);
println!("{:?}", a * 2)
} Instead of that "just working" you now get the following error, which doesn't even give the hint that dereferencing might help:
It really only impacts the
That is true, but again. This only impacts the |
## Synopsis While looking into #328 I realized the current situation around Pointer derives and references was even weirder because we store a double-reference to the fields in the local variables for Debug, but not for Display. The reason we were doing this was because of #289. ## Solution This stops storing a double-reference, and only adds the additional reference in the places where its needed.
Closing this one in favor if #381 |
@JelteF sorry for bringing this up again, but I have more thoughts about it.
We could try leveraging this fact. Does the following algo makes sense?
This way, we use the |
Okay I played around with that idea a bit more, but I cannot get it to do the thing you say it should do for anything but the most basic expression. The following for instance fails to compile, because there's now a PtrWrappper around the use std::fmt;
#[repr(transparent)]
pub struct PtrWrapper<T> (pub T);
impl<T: fmt::Pointer> fmt::Pointer for PtrWrapper<&T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(*self.0).fmt(f)
}
}
impl<T> ::core::ops::Deref for PtrWrapper<&T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
fn main() {
// a is the field
let a: &i32 = &123;
let a = &a; // this is our local variable
println!("{:p}, {:p}", a, unsafe { (*a as *const i32).add(1) });
println!("{:p}, {:p}", PtrWrapper(a), PtrWrapper(unsafe { (*a as *const i32).add(1) }));
} the error:
Unless you can get this working, I don't really see the point of adding this. The downsides it brings (stuff not compiling and being more complicated to explain) seem worse than the benefits (not having to put a |
For named arguments there would be no need to use |
Finally, I even if you can get |
Yeah... still imperfect. In complex expressions, better people realize they're working with a reference rather than a direct value. Trying to hide that fact will lead to very subtle bugs if we cannot strip out that additional indirection in all the cases. And we cannot, because we cannot overload "reading the value" in Rust in any way. |
Resolves #328 Requires #377 Requires #380 ## Synopsis `Debug` and `Display` derives allow referring fields via short syntax (`_0` for unnamed fields and `name` for named fields): ```rust #[derive(Display)] #[display("{_0:o}")] struct OctalInt(i32); ``` The way this works is by introducing a local binding in the macro expansion: ```rust let _0 = &self.0; ``` This, however, introduces double pointer indirection. For most of the `fmt` traits, this is totally OK. However, the `fmt::Pointer` is sensitive to that: ```rust #[derive(Display)] #[display("--> {_0:p}")] struct Int(&'static i32); // expands to impl fmt::Display for Int { fn fmt(&self, f: fmt::Formatter<'_>) -> fmt::Result { let _0 = &self.0; // has `&&i32` type, not `&i32` write!(f, "--> {_0:p}") // so, prints address of the `_0` local binding, // not the one of the `self.0` field as we would expect } } ``` ## Solution Pass all local bindings also as named parameters and dereference them there. This allows `"{_0:p}"` to work as expected. Positional arguments and expressions still have the previous behaviour. This seems okay IMHO, as we can explain that in expressions these local bindings are references and that you need to dereference when needed, such as for `Pointer`. A downside of the current implementation is that users cannot use the names of our named parameters as names for their own named parameters, because we already use them. With some additional code this is fixable, but it doesn't seem important enough to fix. People can simply use a different name when creating their own named parameters, which is a good idea anyway because it will be less confusing to any reader of the code. If it turns out to be important to support this after all, we can still start to support it in a backwards compatible way (because now it causes a compilation failure). Co-authored-by: Kai Ren <[email protected]>
Synopsis
Debug
andDisplay
derives allow referring fields via short syntax (_0
for unnamed fields andname
for named fields):The way this works is by introducing a local binding in the macro expansion:
This, however, introduces double pointer indirection. For most of the
fmt
traits, this is totally OK. However, thefmt::Pointer
is sensitive to that:Solution
Instead of introducing local bindings, try to replace this shortcuts (like
_0
) in idents and expressions with a field accessing syntax (likeself.0
). Or event just substitute_0
with(*_0)
, because for enum variants we cannot really access the fields viaself.
.Checklist
Documentation is updated(not required)