-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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 #99684 through autoref in write!
macro with a two-phased borrows retrocompat workaround
#100202
Fix #99684 through autoref in write!
macro with a two-phased borrows retrocompat workaround
#100202
Conversation
I think it could be interesting to have a crater run for this approach, seems it seems better than reverting dtolnay's drop-temporaries improvements |
This comment has been minimized.
This comment has been minimized.
a05ef4f
to
daa955d
Compare
This comment has been minimized.
This comment has been minimized.
daa955d
to
6104067
Compare
Is there a typo here? I'm pretty sure my approach doesn't handle two-phased borrows, just the autoref semantics of not needing Also, for the record, there are possible variations on both the names of any of the helper definitions, and whether the generic parameter "seal" hack uses a Since some macro shenanigans are involved, cc @petrochenkov. |
This comment has been minimized.
This comment has been minimized.
I had phrased it ambiguously, I've edited to clarify:
Footnotes
|
6104067
to
1226330
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks promising to me.
Some edge cases where this would still cause breakage— unclear how widespread these would be.
write_fmt
that takes self by value. The autoref thing indiscriminately turns everything into a reference.
use std::fmt;
struct Out;
impl Out {
fn write_fmt(self, _args: fmt::Arguments) {
unimplemented!()
}
}
fn main() {
let out = || Out;
write!(out(), "...");
}
error[E0507]: cannot move out of `*_dst` which is behind a mutable reference
--> src/main.rs:13:5
|
13 | write!(out(), "...");
| ^^^^^^^^^^^^^^^^^^^^ move occurs because `*_dst` has type `Out`, which does not implement the `Copy` trait
|
= note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
- 2φ borrows within a tuple struct (as you already called out).
use std::fmt;
struct Out {
val: usize,
}
impl Out {
fn write_fmt(&mut self, _args: fmt::Arguments) {
unimplemented!()
}
}
struct Thing(Out);
fn main() {
let mut thing = Thing(Out { val: 0 });
write!(thing.0, "{}", {thing.0.val});
}
error[E0503]: cannot use `thing.0.val` because it was mutably borrowed
--> src/main.rs:18:28
|
18 | write!(thing.0, "{}", {thing.0.val});
| -----------------------^^^^^^^^^^^--
| | |
| | use of borrowed `thing.0`
| borrow of `thing.0` occurs here
| borrow later used by call
- 2φ borrows which are not of the form
$($dst_place:tt).+
.
use std::fmt;
struct Out {
val: usize,
}
impl Out {
fn write_fmt(&mut self, _args: fmt::Arguments) {
unimplemented!()
}
}
fn main() {
let mut out = [Out { val: 0 }];
write!(out[0], "{}", {out[0].val});
}
error[E0503]: cannot use `out[_].val` because it was mutably borrowed
--> src/main.rs:16:27
|
16 | write!(out[0], "{}", {out[0].val});
| ----------------------^^^^^^^^^^--
| | |
| | use of borrowed `out[_]`
| borrow of `out[_]` occurs here
| borrow later used by call
This comment has been minimized.
This comment has been minimized.
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
Now that tuple indexing is covered as well, I feel more confident about this PR 🙂. Regarding commit history, I was thinking of squashing all the intermediary commits together, to end up with something along the lines of:
Thoughts? |
a7a9a3f
to
64a147c
Compare
This comment has been minimized.
This comment has been minimized.
64a147c
to
b3bdbef
Compare
write!
macro with a two-phased borrows retrocompat hackwrite!
macro with a two-phased borrows retrocompat workaround
b3bdbef
to
4fbfec6
Compare
(cleaned up the commit history; the original one is available at b3bdbef) |
d877e70
to
38a6d3b
Compare
($dst:expr, $($arg:tt)*) => { | ||
$dst.write_fmt($crate::format_args_nl!($($arg)*)) | ||
match $crate::ops::autoref::autoref_mut!($dst) { | ||
mut _dst => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_dst
is guaranteed to be &mut _
, doesn't that mean the mut
here is redundant?
(Also, is that why you had to add a _
prefix? Doesn't really seem needed otherwise AFAICT)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
triage:
@danielhenrymantilla ☝️ - can you address this? thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, sorry, I had completely forgotten about this comment 😅. So the thing is that sometimes you do need a nested &mut
, such as for the [u8] :! Write
-but-&'_ mut [u8] : Write
case:
let mut buf: &mut [u8] = ...;
buf.write_fmt(...) // resolves to `<&mut [u8] as io::Write>::write_fmt(&mut buf : &mut &mut [u8], ...)`
// vs
match buf.autoref() { // here, the identity function: `match identity::<&mut [u8]>(buf) {`
_buf: &mut [u8] => _buf.write_fmt(...), // we need the *nested* `&mut` => `_buf` needs to be `mut`
// <&mut [u8] as io::Write>::write_fmt(&mut _buf, ...)
So the mut
is necessary here, but since it's very often unnecessary as well, we need to use either #[allow(unused_mut)]
or a _
-prefixed binding name (ideally this macro-generated stuff would not lint altogether, although I guess it's not possible for core
/ the crate where the macro is defined).
I went for _
-prefixed as a personal choice since it yields slightly more lightweight code, but I can very well change it to the allow
This comment has been minimized.
This comment has been minimized.
…illa Fix stability annotations `autoref!` cleanup: improve comments, and move it under `core::ops`
`write!`: tweak the 2φ borrows hack to cover tuple indexing too
5cbe233
to
b84d25d
Compare
The job Click to see the possible cause of the failure (guessed by this bot)
|
☔ The latest upstream changes (presumably #105667) made this pull request unmergeable. Please resolve the merge conflicts. |
r? @pnkfelix |
I've started doing some review of this PR. My current slight concern is that there seems like there was a broad set of behaviors presented in the tables on the descriptions of PR #96455 and PR #99689, but this specific PR shows only one existing test changing its behavior, namely: Also, this PR rewrites that existing test rather than adding a new case to it. I'm not sure whether the change to the existing test is a pure generalization, or is meant to make the overall behavior clearer, or is a fundamental change to what cases are covered by the test (which would be bad if the new set of cases is not a superset of the old set of cases). I'm still digging in, since I haven't fully grokked how to map the tables from PR #96455 and PR #99689 into the actual test code that landed in PR #99689; I'm hoping the fact that the single changed test in this PR (PR #100202) is one of the tests added in PR #99689 is actually a good sign in terms of overall coverage here. So, I will keep digging and establish how to map the tables to the tests, and then work my way up from there. |
|
||
use std::fmt::{self, Display}; | ||
|
||
struct Mutex; | ||
|
||
impl Mutex { | ||
fn lock(&self) -> MutexGuard { | ||
MutexGuard(self) | ||
/// Dependent item with (potential) drop glue to disable NLL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of rewriting the test in this manner (which I believe was intended to show a generalization of the desired behavior), I recommend keeping the existing test structure (i.e. have fn lock
return MutexGuard
(where the <'a>
is elided in the return type signature), and then add your variant (that instead returns impl '_ + Display
) as a second separate structure in this same single test file. That way, you get the coverage you're looking for, but people who are looking through the history of this issue will be able to more immediately map the original discussions to the MutexGuard
test that will remain encoded in the test.
I think it would be good to encode the concrete demo demonstrating the change in order that this PR causes to happen. You can see an explanation of it for (I'd recommend adapting that code into a test on its own, and then generalizing it to cover But, since I'm not involved on T-libs, I'm not certain whether they were making a deliberate choice to not encode this as a test, so I opened up a zulip topic asking if this was a choice to leave it unencoded as a test, or just considered not important to test directly; see here: https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/drop.20timing.20for.20write.2Fprint.20macros.20.2396455/near/327621973 |
Also, the build is failing, as described in the CI comment above Moving this back to S-waiting-on-author @rustbot label: +S-waiting-on-author -S-waiting-on-review |
Hmm, I'm a bit out of bandwidth to go back to this, especially given how the initial motivation of the PR (avoiding a revert which afterwards ended up happening anyways) is no longer there: at that point the "hack" in this PR may no longer be worth it. Thanks for the review @pnkfelix, and I apologize for having made you spend time on it 🙇 (in hindsight I should have closed this earlier, but it just totally fell off my radar) |
Fixes #99684, using the idea suggested over #99684 (comment) by @eddyb, and implemented by @dtolnay to test its viability.
On top of that, it does use a two phased borrows retrocompat workaround (imho, the
write!
family of macros supporting two-phased borrows to begin with is quite surprising —could we have a FCW against it?).write!
);EDIT: as it is implemented now, it should be quite robust and tackle the expected semantics 🙂