-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Is there a gentler way to land the assert_matches macro? #82913
Comments
cc @petrochenkov for resolution |
I've prepared a change which should allow us to update to the current nightly and might give a sense of the frequency within our codebase (more than I'd expect but less than I'd feared). I'm not sure to what extent that holds for the rest of the ecosystem, maybe a crater run would be useful to determine the severity here. |
This was a big enough breakage for Fuchsia (and the error messages are confusing enough) that I think a crater run is warranted. I'm not sure if the release team wants to weigh in at this point or not, but nominating for discussion in the release team meeting. @rustbot label: +T-release +I-nominated |
We discussed this in today's @rust-lang/libs meeting. We'd propose the following:
|
(I think the libs team decision makes this a bit moot, but perhaps not) I think for unexpected, extensive breakage it usually makes sense to revert and perhaps reland with no changes, just giving folks a chance to evaluate the breakage - potentially via crater. I think the compiler team has been moving towards reverting eagerly, too, though there's not been set policy yet, and I think there's not quite consensus. I'll leave this nominated in case others on the release team have thoughts and bring it up in our meeting too. |
It looks like the removal here hasn't happened, and has now hit beta. Tagging as a beta regression. |
#84759 takes care of the regression in beta (1.52), but we'll need a similar revert for 1.53 (branching ~today), and a proper fix for 1.54. |
|
I'm not sure if this is a good way forward. We could definitely move it into a submodule, but making this an edition change (including a migration lint etc.) seems like too much effort for the small breakage this caused. Having As Mark suggested above, we could do the same as we did for ping @rust-lang/libs for opinions |
Landing this despite the breakage feels not so great to me. Not horrible. But not great. In particular, it looks like the root cause to the breakage is import behavior. Which I think also in turn means that adding any new macro to the prelude could potentially result in this sort of breakage, which seems really quite unfortunate. (Did this happen when the With that said, can we do another crater run to see if the breakage has actually been fixed? If it has, then that might indeed be the right way to go here. But I'm still worried about the longer term problem with imports.
Yeah I hear this. Having to import |
Indeed. Maybe that behavior could be fixed? Quoting myself from SimonSapin/rust-std-candidates#22 (comment):
|
Yes: #66518 (That problem was 'resolved' by ignoring it until it was too late because it reached the stable compiler: #66518 (comment))
Apparetly that's tricky: #65721 (comment)
It'd be pretty much trivial to add it only in |
@m-ou-se I'm confused by this -- if we did it in an edition, wouldn't all 3 be available? What makes this so hard to do in an edition? (I haven't thought about what migration is required) |
Oh, the comment about And doing this edition trick won't work the next time when we run into this problem (without waiting three years). So I'd prefer some conslusion that we can re-use when this happens again in a few months or so. |
Update: We don't have a solution here yet, and nightly is now at 1.55. So if we want to avoid the breakage, we need a backport again (to 1.54). |
We discussed this today, and concluded that our best options here are either:
To be discussed in the edition meeting on monday. |
We discussed this in the edition meeting, and weren't too enthusiastic about making this an edition change without also adding an automatic migration for it. Breakage on upgrading to Rust 2021 without automatic migration is a reason for not upgrading at all, which we want to avoid. Adding a migration lint for this is also not easy. @nikomatsakis encouraged a discussion of this general issue (macros from the std root/prelude breaking things) in a language meeting to see if something can be done about this problem. (As was suggested by @BurntSushi above.) For now, we should move this macro to e.g. |
…, r=yaahc Move assert_matches to an inner module Fixes rust-lang#82913
Going to reopen to track the beta backport. |
Beta backport landed. Closing this. |
…ro, r=m-ou-se Add a stack-`pin!`-ning macro to `core::pin`. - rust-lang#93178 `pin!` allows pinning a value to the stack. Thanks to being implemented in the stdlib, which gives access to `macro` macros, and to the private `.pointer` field of the `Pin` wrapper, [it was recently discovered](https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async-foundations/topic/pin!.20.E2.80.94.20the.20.22definitive.22.20edition.20.28a.20rhs-compatible.20pin-nin.2E.2E.2E/near/268731241) ([archive link](https://zulip-archive.rust-lang.org/stream/187312-wg-async-foundations/topic/A.20rhs-compatible.20pin-ning.20macro.html#268731241)), contrary to popular belief, that it is actually possible to implement and feature such a macro: ```rust let foo: Pin<&mut PhantomPinned> = pin!(PhantomPinned); stuff(foo); ``` or, directly: ```rust stuff(pin!(PhantomPinned)); ``` - For context, historically, this used to require one of the two following syntaxes: - ```rust let foo = PhantomPinned; pin!(foo); stuff(foo); ``` - ```rust pin! { let foo = PhantomPinned; } stuff(foo); ``` This macro thus allows, for instance, doing things like: ```diff fn block_on<T>(fut: impl Future<Output = T>) -> T { // Pin the future so it can be polled. - let mut fut = Box::pin(fut); + let mut fut = pin!(fut); // Create a new context to be passed to the future. let t = thread::current(); let waker = Arc::new(ThreadWaker(t)).into(); let mut cx = Context::from_waker(&waker); // Run the future to completion. loop { match fut.as_mut().poll(&mut cx) { Poll::Ready(res) => return res, Poll::Pending => thread::park(), } } } ``` - _c.f._, https://doc.rust-lang.org/1.58.1/alloc/task/trait.Wake.html And so on, and so forth. I don't think such an API can get better than that, barring full featured language support (`&pin` references or something), so I see no reason not to start experimenting with featuring this in the stdlib already 🙂 - cc `@rust-lang/wg-async-foundations` \[EDIT: this doesn't seem to have pinged anybody 😩, thanks `@yoshuawuyts` for the real ping\] r? `@joshtriplett` ___ # Docs preview https://user-images.githubusercontent.com/9920355/150605731-1f45c2eb-c9b0-4ce3-b17f-2784fb75786e.mp4 ___ # Implementation The implementation ends up being dead simple (so much it's embarrassing): ```rust pub macro pin($value:expr $(,)?) { Pin { pointer: &mut { $value } } } ``` _and voilà_! - The key for it working lies in [the rules governing the scope of anonymous temporaries](https://doc.rust-lang.org/1.58.1/reference/destructors.html#temporary-lifetime-extension). <details><summary>Comments and context</summary> This is `Pin::new_unchecked(&mut { $value })`, so, for starters, let's review such a hypothetical macro (that any user-code could define): ```rust macro_rules! pin {( $value:expr ) => ( match &mut { $value } { at_value => unsafe { // Do not wrap `$value` in an `unsafe` block. $crate::pin::Pin::<&mut _>::new_unchecked(at_value) }} )} ``` Safety: - `type P = &mut _`. There are thus no pathological `Deref{,Mut}` impls that would break `Pin`'s invariants. - `{ $value }` is braced, making it a _block expression_, thus **moving** the given `$value`, and making it _become an **anonymous** temporary_. By virtue of being anonynomous, it can no longer be accessed, thus preventing any attemps to `mem::replace` it or `mem::forget` it, _etc._ This gives us a `pin!` definition that is sound, and which works, but only in certain scenarios: - If the `pin!(value)` expression is _directly_ fed to a function call: `let poll = pin!(fut).poll(cx);` - If the `pin!(value)` expression is part of a scrutinee: ```rust match pin!(fut) { pinned_fut => { pinned_fut.as_mut().poll(...); pinned_fut.as_mut().poll(...); }} // <- `fut` is dropped here. ``` Alas, it doesn't work for the more straight-forward use-case: `let` bindings. ```rust let pinned_fut = pin!(fut); // <- temporary value is freed at the end of this statement pinned_fut.poll(...) // error[E0716]: temporary value dropped while borrowed // note: consider using a `let` binding to create a longer lived value ``` - Issues such as this one are the ones motivating rust-lang/rfcs#66 This makes such a macro incredibly unergonomic in practice, and the reason most macros out there had to take the path of being a statement/binding macro (_e.g._, `pin!(future);`) instead of featuring the more intuitive ergonomics of an expression macro. Luckily, there is a way to avoid the problem. Indeed, the problem stems from the fact that a temporary is dropped at the end of its enclosing statement when it is part of the parameters given to function call, which has precisely been the case with our `Pin::new_unchecked()`! For instance, ```rust let p = Pin::new_unchecked(&mut <temporary>); ``` becomes: ```rust let p = { let mut anon = <temporary>; &mut anon }; ``` However, when using a literal braced struct to construct the value, references to temporaries can then be taken. This makes Rust change the lifespan of such temporaries so that they are, instead, dropped _at the end of the enscoping block_. For instance, ```rust let p = Pin { pointer: &mut <temporary> }; ``` becomes: ```rust let mut anon = <temporary>; let p = Pin { pointer: &mut anon }; ``` which is *exactly* what we want. Finally, we don't hit problems _w.r.t._ the privacy of the `pointer` field, or the unqualified `Pin` name, thanks to `decl_macro`s being _fully_ hygienic (`def_site` hygiene). </details> ___ # TODO - [x] Add compile-fail tests with attempts to break the `Pin` invariants thanks to the macro (_e.g._, try to access the private `.pointer` field, or see what happens if such a pin is used outside its enscoping scope (borrow error)); - [ ] Follow-up stuff: - [ ] Try to experiment with adding `pin!` to the prelude: this may require to be handled with some extra care, as it may lead to issues reminiscent of those of `assert_matches!`: rust-lang#82913 - [x] Create the tracking issue.
As a user I'm really excited for
std::assert_matches!
, but I'm a little worried about the level of breakage it introduces and I'm hoping to discuss possible paths forward.The Fuchsia roll to update rustc is currently blocked by ambiguous macro resolution errors with code that glob-imports
matches::assert_matches!
. It seems that a locally defined glob import does not take precedence over macros defined in std.This implies a workaround of changing all the glob imports to
use matches::assert_matches
, but this does not account for submodules whichuse super::*;
. Unfortunately it seems that the test_case crate wraps the function body in a submodule and adds ause super::*
to the new module.What this means in practice is that every function using the test_case crate must have an explicit import added or have the macro referenced via a fully qualified name. AIUI this change counts as an acceptable breakage and so I'm still putting together a diff which will resolve all of our roll blockers.
What I'm wondering though is whether knowing about this source of breakage offers a path to a smoother release. If so, we could revert the change and prepare to roll it out more gently.
Two options that came up in private discussions:
Any other suggestions for how to make this change less disruptive? cc @m-ou-se @tmandry @joshtriplett
The text was updated successfully, but these errors were encountered: