-
Notifications
You must be signed in to change notification settings - Fork 13.7k
Arbitrary self types v2: stabilize #135881
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
Arbitrary self types v2: stabilize #135881
Conversation
This comment has been minimized.
This comment has been minimized.
@rustbot label -S-waiting-on-review
78dbe11
to
1ce21e5
Compare
This comment has been minimized.
This comment has been minimized.
1ce21e5
to
0d8e767
Compare
This comment has been minimized.
This comment has been minimized.
0d8e767
to
317df48
Compare
Thanks for this! I already see an edit to the template that could be useful, clarifying what static checks are required and linking to tests that demonstrate them, but it kind of duplicates the reference/spec work
So the report mentions that testing already happened in a few forms.
How interested would you be in other libraries using this feature? We could implement it in Masonry, but since we don't want our crate to be nightly-only, we'd have to either use a feature flag or keep it to a separate branch.
If we do end up writing an implementation, is there any kind of feedback you'd be specifically interested in?
How interested would you be in other libraries using this feature? We could implement it in Masonry, but since we don't want our crate to be nightly-only, we'd have to either use a feature flag or keep it to a separate branch.
Good question and thanks for the offer! I personally have run out of runway to work on this feature - so I'm keeping my fingers crossed that no major semantic changes are needed. Or if they are, it will need someone else to pick up the work. So, the most interesting and useful testing to me would be looking for corner cases or oddities in the current implementation which cause ICEs or other oddities. (An example is the interaction with CoercePointee
which caused a crash). It's hard for me to guess how likely it is that you'd find any such problem. Obviously I hope you wouldn't, but real world testing always has a habit of finding fun surprises.
Overall: up to you! If you think it would be useful for Masonry long-term then perhaps it's worth having a play in the hopes and expectations that this will be stabilized soon and you can therefore merge it into your main branch before too long.
I'd like to ask that the cargo SemVer reference be updated to describe any new SemVer hazards that are introduced here. This doesn't have to be a stabilization blocker, but I'd appreciate it if it can be done at least shortly thereafter so I can make sure cargo-semver-checks
quickly offers the best possible support for linting this excellent new functionality.
You probably want to add a #[cfg_attr(bootstrap, doc = "#[feature(arbitrary_self_types)]")]
or however you spell it, so that you can conditionally gate whatever doc test under the feature gate when bootstrapping. Or just copy the item and remove the doctest from the bootstrap version.
For now I'm going to mark the doctest compile_fail
just to see if we get past the miri problems and to see what else crops up.
a415b41
to
e6e8529
Compare
This comment has been minimized.
This comment has been minimized.
e6e8529
to
3825ac6
Compare
For now, this disables a doctest, which otherwise fails. We'll want to re-enable that later.
3825ac6
to
f0a0065
Compare
☔ The latest upstream changes (presumably #138208) made this pull request unmergeable. Please resolve the merge conflicts.
...ject-hard-error, r=oli-obk Make `ptr_cast_add_auto_to_object` lint into hard error In Rust 1.81, we added a FCW lint (including linting in dependencies) against pointer casts that add an auto trait to dyn bounds. This was part of work making casts of pointers involving trait objects stricter, and was part of the work needed to restabilize trait upcasting. We considered just making this a hard error, but opted against it at that time due to breakage found by crater. This breakage was mostly due to the `anymap` crate which has been a persistent problem for us. It's now a year later, and the fact that this is not yet a hard error is giving us pause about stabilizing arbitrary self types and `derive(CoercePointee)`. So let's see about making a hard error of this. r? ghost cc ```@adetaylor``` ```@Darksonn``` ```@BoxyUwU``` ```@RalfJung``` ```@compiler-errors``` ```@oli-obk``` ```@WaffleLapkin``` Related: - rust-lang/rust#135881 - rust-lang/rust#136702 - rust-lang/rust#136776 Tracking: - rust-lang/rust#127323 - rust-lang/rust#44874 - rust-lang/rust#123430
Thrilled to see the amazing work being done here and in related PRs. My most sincere thanks and admiration for everyone helping push this work forward 🙇
I was playing with the arbitrary_self_types
feature today as part of some cargo-semver-checks
work, and I noticed a behavior I found surprising: Pin<P>
as a receiver but does not directly allow setting P
to a custom impl Receiver
type. For example: playground
#![feature(arbitrary_self_types)] pub struct Example(i64); pub struct CustomReceiver<T>(T); impl<T> std::ops::Receiver for CustomReceiver<T> { type Target = T; } impl Example { pub fn by_pinned_custom_receiver(self: std::pin::Pin<CustomReceiver<Self>>) {} }
produces the following error:
error[E0307]: invalid `self` parameter type: `Pin<CustomReceiver<Example>>`
--> src/lib.rs:12:44
|
12 | pub fn by_pinned_custom_receiver(self: std::pin::Pin<CustomReceiver<Self>>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: type of `self` must be `Self` or some type implementing `Receiver`
= help: consider changing to `self`, `&self`, `&mut self`, or a type implementing `Receiver` such as `self: Box<Self>`, `self: Rc<Self>`, or `self: Arc<Self>`
For more information about this error, try `rustc --explain E0307`.
Changing self: std::pin::Pin<CustomReceiver<Self>>
to self: std::pin::Pin<&mut CustomReceiver<Self>>
(i.e. changing Pin<P>
from P = CustomReceiver<Self>
to P = &mut CustomReceiver<Self>
) avoids the error.
Perhaps this is entirely expected, and it is merely my naïveté causing me to be surprised. I thought I'd flag it on the off chance it either is genuinely unexpected, or deserving of closer documentation than the present trait Receiver
and Pin
-as-receiver-type documentation currently offer.
Yeah, we can't impl<T: Receiver> Receiver for Pin<T>
because of the blanket Deref
impl (I think it's the same underlying issue as discussed further up, see in particular this response).
Neat, so it sounds like this is a known issue — thank you. This implication of the blanket Deref
impl wasn't obvious to me. Is this worth specifically calling out in docs? I'll leave that question in the capable hands of everyone here 🙏
Thanks again for driving this amazing work forward!
That is a particularly unfortunate and motivating example of it though. Thanks for mentioning that @obi1kenobi.
Here's that spelled out a bit:
That is motivating enough we should discuss it -- I don't recall us ever considering this one, and it is bad -- to see how we might eventually be able to address it.
@rustbot labels +I-lang-nominated
@rfcbot concern discuss-plan-for-pin
I started a conversation on maybe allowing overlapping impls only for a impl<T: ?Sized + Receiver> Receiver for Pin<T>
in core
:
#t-types > impl Receiver for Pin and overlapping impls
alternative idea: #t-lang > impl Receiver for Pin and impl Receiver for impl Deref
We discussed this Pin
interaction in the lang team meeting. I have to admit I'm a bit worried about the relationship between Deref
and Receiver
, we may be missing something here.
I feel like the actual design we want is to have these two traits:
trait Deref: Receiver { fn deref(&self) -> &Self::Target; } trait Receiver { type Target; }
and then to have an impl Deref for Foo { type Target = X; }
be equivalent two implementing both Deref
and Receiver
. This idea of having an impl be able to implement supertraits if it specifies their items is one that I think a lot of us want, but is obviously a distinct feature worthy of discussion on its own. For the moment, let's imagine that we just, for backwards compatibility reason, hardcode the behavior that writing an impl Deref
that includes Target
expands to both an impl Deref
and an implementation of Receiver
.
If we did this, I think it actually works very well for most smart pointer types:
struct MyPointer<T> { } impl<T: ?Sized> Deref for MyPointer<T> { type Target = T; fn deref(&self) -> Self::Target { ... } } // Converted to impl<T: ?Sized> Deref for MyPointer<T> { fn deref(&self) -> Self::Target { ... } } impl<T: ?Sized> Receiver for MyPointer<T> { type Target = T; }
If however you have your own Pin
-like type, you get a problem:
struct MyPin<T> { } impl<P: Deref> Deref for MyPin<P> { type Target = P::Target; fn deref(&self) -> } // Converted to impl<T: Deref> Deref for MyPointer<T> { fn deref(&self) -> Self::Target { ... } } impl<T: Deref> Receiver for MyPointer<T> { type Target = T; }
...but you probably wanted that final impl to be
impl<T: Receiver> Receiver for MyPointer<T> { type Target = T; }
However, this is not obviously a problem. I believe you can just break the impl into two impls.
Proposal in short
- Change to
Deref: Receiver
and moveTarget
toReceiver
- In compiler front-end, split apart impls of
Deref
that defineTarget
to impl bothDeref
andReceiver
Questions then:
- For types team, does the above seem reasonable? Am I missing something?
- I am also assuming that
<T as Deref>::Target
continues to work ifDeref: Receiver
andTarget
is located inReceiver
, but I can't 100% remember if that's true - For lang team would seem to be: are we willing to special case impls of
Deref
on the premise that we will generalize this later? It's a bit of a "confidence call". And, if so, can/should we prioritize this work? - And in general, given that this is a non-trivial change, how do we feel about pushing back the stabilization schedule on this.
- I am also assuming that
<T as Deref>::Target
continues to work ifDeref: Receiver
andTarget
is located inReceiver
, but I can't 100% remember if that's true
that does not work, so we'd need a special exception to make it work.
@programmerjake I think we could (and should) make it work then, yes.
...mpl, r=<try> arbitrary_self_type: insert implied Receiver bound on Deref r? `@nikomatsakis` This is an experimental setup which allows us to assess the impact from inserting an implied supertrait bound `Deref: Receiver`. The observations are as follows. - Thanks to the trait solver, not too many hacks are needed to implement the idea behind [this comment](rust-lang#135881 (comment)), where inductive cycles are admissible. - We want to allow users to continue use the `dyn Deref` type as `Deref` has been `dyn`-compatible. Following the current `dyn`-compatibility rule, it would have been impossible to use `dyn Deref<Target = ..>` because one would need to write `dyn Deref<..> + Receiver<..>` while neither `Deref` nor `Receiver` is an `auto` trait. This is the main change that this PR proposes: we fill in the missing projection bound that `Receiver` demands from a `dyn Deref` and this will remain the only exception. - Finally, the type (pretty-)printing is adjusted so as to not present the frivolous `Receiver::Target = ..` bound. It will always be the same term as `Deref::Target`. I am proposing a crater experiment to see the extent of impact on the existing ecosystem and explore corner cases that I have missed.
While working on opaque types in the new solver, I encountered https://github.com/rust-lang/rust/blob/master/tests/ui/methods/call_method_unknown_referent2.rs
#![feature(arbitrary_self_types)] struct SmartPtr<T>(T); impl<T> core::ops::Receiver for SmartPtr<T> { type Target = T; } impl<T> SmartPtr<T> { fn foo(&self) -> usize { 3 } } fn main() { let ptr = SmartPtr(Default::default()); // Ensure calls to outer methods work even if inner methods can't be // resolved due to the type variable assert_eq!(ptr.foo(), 3); let _: SmartPtr<u32> = ptr; }
This behavior was surprising to me as it differs from autoderef steps via Deref
#![feature(arbitrary_self_types)] use std::ops::Deref; struct SmartPtr<T>(T); impl<T> Deref for SmartPtr<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl<T> SmartPtr<T> { fn foo(&self) -> usize { 3 } } fn main() { let ptr = SmartPtr(Default::default()); assert_eq!(ptr.foo(), 3); //~ ERROR type annotations needed for `SmartPtr<_>` let _: SmartPtr<u32> = ptr; }
It also diverges from the existing lint/hard error for raw-pointers
edition 2015: emits a lint
edition 2018: results in a hard error
#![feature(arbitrary_self_types)] #![feature(rustc_attrs)] impl<T> *const T { #[rustc_allow_incoherent_impl] fn foo(&self) -> usize { 3 } } fn main() { let ptr = std::ptr::null(); assert_eq!(ptr.foo(), 3); let _: *const u32 = ptr; }
This is caused by the following uncommented line which always uses the final type from the Deref
chain instead of the potentially longer Receiver
chain.
Is this intended behavior?
There's a Zulip thread discussing if Deref
and Receiver
should be related at all via trait bounds or blanket impls. I now think they should be unrelated.
Uh oh!
There was an error while loading. Please reload this page.
This PR stabilizes the arbitrary self types v2 feature, tracked in #44874.
r? @wesleywiser
Stabilization report
I'd like to stabilize Arbitrary Self Types in some upcoming Rust version. A stabilization PR is here. What follows is the list of questions from Niko's new template plus various sections I've seen in other stabilization reports.
Summary
This feature allows custom smart pointer types to be used as method receivers:
What is the RFC for this feature and what changes have occurred to the user-facing design since the RFC was finalized?
Changes since the RFC:
T: ?Sized
bound - this is in progress here - @cramertj has kindly taken this on, and has determined that this is a pre-existing more general problem which she'll work on. We do not need to block stabilization to wait for this. (The other diagnostics noted in the RFC have been added).In two cases the RFC wasn't very specific and the implementation has required some choices:
What behavior are we committing to that has been controversial? Summarize the major arguments pro/con.
In general, Arbitrary Self Types is the opposite of controversial. It has always been anomalous that some specific stdlib smart pointer types have been hard-coded; this work removes that hard-coding.
The specific points which have involved discussion during the process:
Deref
trait. This version introduces a newReceiver
trait, such that types can be used asself
types even if they can't safely devolve to a reference (e.g. because they're a wrapper for a pointer that can't safely comply with Rust reference semantics). This has been largely uncontroversial, but I'd note there's a blanket implementation ofReceiver
forT: Deref
. This ensures all existing smart pointer types continue to work, and reduces complexity in users' brains. I'm 100% sure that this is the right thing to do, but highlighting it here because blanket impls are fairly unusual in the Rust standard library.Rc
,Box
- because we may generate errors if they match the name of methods in pointees. It's already good practice to avoid these because of the risk of shadowing pointee methods, so these types tend to have associated functions instead. Now, any such shadowing will generate an error instead of silently shadowing.Rc
,Box
etc. and allow smart pointer types such asNonNull
andWeak
. We decided not to do that.Are there extensions to this feature that remain unstable? How do we know that we are not accidentally committing to those.
There is an
arbitrary_self_types_pointers
feature gate which allows raw pointers to be used as self types. Arguments against using this are summarized here.This was a pre-existing aspect of the former
arbitrary_self_types
feature gate which has been split out into its own new feature gate because we don't want to stabilize it at this time, but we don't feel a strong need to remove this option from nightly Rust users.Summarize the major parts of the implementation and provide links into the code (or to PRs)
The three main PRs are:
Receiver
traitReceiver
instead ofDeref
; adds the deshadowing algorithm which is responsible for most of the complexitySummarize existing test coverage of this feature
Arc
type and has partially adapted to the changes hereHas a call-for-testing period been conducted? If so, what feedback was received?
No; though an earlier version of arbitrary self types has been in nightly for years. This has been experimented with by multiple communities - Rust/C++ interop, Rust/Python interop, and Rust for Linux. As the RFC notes, v2 was proposed based on experiences with v1.
What outstanding bugs in the issue tracker involve this feature? Are they stabilization-blocking?
DispatchFromDyn
and the being-stabilizedCoercePointee
. This should block stabilization of either this feature, orCoercePointee
.Summarize contributors to the feature by name for recognition and assuredness that people involved in the feature agree with stabilization
Very sorry to those who I've missed; it's been a long road :)
What FIXMEs are still in the code for that feature and why is it ok to leave them there?
None.
What static checks are done that are needed to prevent undefined behavior?
None.
In what way does this feature interact with the reference/specification, and are those edits prepared
Does this feature introduce new expressions and can they produce temporaries? What are the lifetimes of those temporaries?
No.
What other unstable features may be exposed by this feature?
None, though the separate
derive(CoercePointee)
becomes more useful when this is also stabilized.What is tooling support like for this feature, rustdoc/clippy/rust-analzyer/rustfmt/etc
I don't anticipate any work here being needed other than for rust-analyzer, tracked here.