add lang items for field projections via virtual places#154940
add lang items for field projections via virtual places#154940BennoLossin wants to merge 20 commits intorust-lang:mainfrom
Conversation
This comment has been minimized.
This comment has been minimized.
e948f37 to
cb85148
Compare
This comment has been minimized.
This comment has been minimized.
cb85148 to
5433734
Compare
|
@rustbot label +F-field_projections |
0f63962 to
1fadbb7
Compare
There was a problem hiding this comment.
Some initial comments... I think similar to the other PR it would help to have some docs guiding review of impls, especially in the absence of Safety docs. Are there any stable-visible implications from such impls (today or intended) even while the traits remain unstable?
Also happy to schedule time to talk about it if that would help.
| /// When the compiler emits the call to `read`, the borrow checker guarantees that no other | ||
| /// operation is conflicting with this one. | ||
| #[lang = "place_read_safety"] | ||
| const SAFETY: bool; |
There was a problem hiding this comment.
I think this interacts with the as-yet unknown Safety comments, but I think we'll want to think about how to define an explanation for what this actually controls. (And in the meantime, I'd love guidance on how to review what value is set here for impls).
If I'm following correctly, we expect that there are actually really two classes of PlaceRead implementations: ones which only need compiler-provided guarantees to call read(), for which SAFETY = true, and a separate class for which the compiler-provided guarantees are insufficient (SAFETY = false).
And the reason we can't used RFC 3245 (refined impls) to meet this is because read() is always itself unsafe to call due to the raw pointer.
If that's accurate, I'm not sure what the second paragraph ("When the compiler emits the call to read, the borrow checker guarantees that no other operation is conflicting with this one.") means in relation to this constant. Are we actually saying that SAFETY = false means borrow checker would not guarantee that?
There was a problem hiding this comment.
Your characterization is spot-on. I no longer follow my own reasoning for having that second paragraph 😅. I think what I wanted to say with that is "if this is set to true, then the safety requirements of the operation are exactly those guaranteed by the compiler when borrow-check succeeds for a program".
Is this better?
/// When the operator is used, the borrow checker follows its usual rules to ensure that no
/// other operation conflicts with this one. If that alone is sufficient to make this operation
/// sound, then this should be `true`.
I still feel like it's a bit cryptic.
As for guiding impls in std, this value should most of the time be true, so for all smart pointers like Box, Arc &mut T etc. Only pointers that don't guarantee that they are always valid will have false here like *mut T, NonNull<T> etc.
| /// When the compiler emits the call to `read`, the borrow checker guarantees that no other | ||
| /// operation is conflicting with this one. | ||
| #[lang = "place_read_safety"] | ||
| const SAFETY: bool; |
There was a problem hiding this comment.
Separately from the other thread, probably not for this PR, it is not clear to me how this can be an associated constant. We want safety to be defined at ~HIR level (at least roughly), I think, and so we don't yet actually know complex constants there. For implementations in the standard library (at least) should we have a restriction that this is always a trivial boolean (false/true) rather than some more complex expression?
There was a problem hiding this comment.
Our current implementation plan is to only allow true or false here. Eventually, we could relax this to allow for expressions that always evaluate to a concrete value when encountering a use of the operator.
|
|
||
| /// Reading a place `let val = *x;`. | ||
| /// | ||
| /// When `x: Self`, then `let val = *x;` will be desugared into [`PlaceRead::read`]. |
There was a problem hiding this comment.
I don't quite see how to work from that expression to the trait invocation. It seems like we need to know S to know which <Self as PlaceRead<S>>::place_read(x, sub) to call, but where is S coming from? Are we expecting to infer S from a unique impl of Subplace::Target for an inferred typeof(val)? That feels somewhat unlikely. (This S point is common across a bunch of the traits here).
Can we perhaps show the intended desugaring here in full?
IIUC, replacing *x with such a desugaring would also generally not be equivalent because that would mean that borrow check stops checking the expression correctly, right? Do we expect that to change at some future date, where borrow check actually always acts on ::place_read calls and there is no 'read' primitive other than that?
There was a problem hiding this comment.
The S is derived from the place expression the operation is applied to. We have a rough sketch of the algorithm, but in this simple example, the desugaring looks like this:
// Let's say the target of `X` is `Y` and that it has a blanket impl of
// `PlaceRead` for all subplaces.
let x: X = ...;
let val = *x;
// desugars to:
let val = unsafe {
<P as PlaceRead<FullSubplace<Y>>>::read(&raw const x)
};
// With `FullSubplace` defined as a ZST and this impl:
impl<T: ?Sized> Subplace for FullSubplace<T> {
type Source = T;
type Target = T;
fn offset(
self,
meta: <T as Pointee>::Metadata,
) -> (usize, <T as Pointee>::Metadata) {
(0, meta)
}
}I don't think it's a good idea to plaster this on every operation trait, we should link to a section in the docs that explains this once. Additionally, this is independent of each operation trait, as this desugaring is attached to the place expression the operation is done on.
My current plan is to implement a set of macros that do the desugaring in the compiler as the next step after the lang items. We already have a rough sketch of the desugaring algorithm. But I'd like to implement it in code before committing it to documentation properly. Would it be okay with you if I added a stub section in the module-level docs of the ops::place submodule that I link to from the traits regarding desugaring? I would fill the stub in the next PR along with the desugaring macros.
Regarding the second point: We will most likely not change the borrow checker to act on PlaceRead::read calls; instead we will likely just change MIR to have a custom read, or change the existing read operation to allow for custom code doing the actual read. So in that sense, the operator isn't actually a desugared before MIR. But I think it's good to teach it that way, since Rust users don't really know about MIR all that much.
| /// The type of the contained place. | ||
| #[lang = "place_target"] | ||
| type Target: ?Sized; | ||
| } |
There was a problem hiding this comment.
Am I interpreting right that this will commonly be implemented for a "Field Representing Type"? Or does this actually not have much relation to that?
There was a problem hiding this comment.
No, this trait will be implemented for (dumb & smart) pointers like Box, Arc, *mut, &mut, NonNull etc. It will also be implemented for place wrapper like MaybeUninit, Cell etc. Anything that conceptually "points at a place" or "contains a place" should implement this trait. Maybe the name Place is a bit misleading after all, especially because Subplace is representing an actual subplace... I'm discussing the name bikeshedding more on zulip.
| /// When `x: Self` and one performs a [`PlaceRead::read`] where the target value is not [`Copy`], | ||
| /// then the compiler checks if this trait is implemented and if so, moves the value out by reading | ||
| /// it and adjusting the borrow checker state of the place. |
There was a problem hiding this comment.
Is it accurate to say that if this trait is not implemented, then moving out of a place is denied by the compiler? i.e., whenever we have let val = *p; we are injecting assert_place_move::<Self>()?
There was a problem hiding this comment.
Correct. But we're only performing the check typeof(p): PlaceMove if the pointee (S::Target) doesn't implement Copy. So we would for example not implement PlaceMove for &T, but you could still "move out" of a &u8, since u8: Copy.
| @@ -0,0 +1,220 @@ | |||
| use crate::field::Subplace; | |||
There was a problem hiding this comment.
I think it would be good to have a module doc -- probably drawing on the recent lang design doc -- that explains Place, Subplace, and the mental model that the individual traits are operations on places.
There was a problem hiding this comment.
That was also my impression. Any ideas for the name of the module? ops::place sounds like a very sensible idea. Alternatively, having it in core::place might also work, but then we're no longer grouping all operators under core::ops. What do you think?
| /// Dropping a place. | ||
| /// | ||
| /// Emitted by the compiler when a place has been partially moved out and the pointer with ownership | ||
| /// is being dropped. |
There was a problem hiding this comment.
I'd love an example desugaring here of the time at which drop(...) gets called and the time at which drop_husk(...) would later(?) get called.
There was a problem hiding this comment.
Sure!
struct Greeter {
greeting: String,
greetee: String,
}
unsafe impl<S> PlaceDrop<S> for Box<S::Source>
where
S: Subplace,
{
/* ... */
}
unsafe impl<T: ?Sized> DropHusk for Box<T> { /* this is the normal drop impl of `Box`! */ }
fn main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: String::from("world"),
});
let _greeting: String = greeter.greeting;
// now the place `*greeter` is partially moved out.
// now `greeter` is dropped here & but before all active subplaces are dropped.
}
// also with "normal" drop-elaboration (I'm just using `drop` for this)
fn desugared_main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: String::from("world"),
});
let _greeting: String = unsafe {
<Box<Greeter> as PlaceRead<proj!(greeter.greeting)>>::read(&raw const greeter)
};
drop(_greeting);
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.greetee)>>::drop(&raw const greeter);
}
unsafe { <Box<Greeter> as DropHusk>::drop_husk(&raw const greeter) };
}I decided to also create a more complex example with dropflags and multiple fields:
Details
struct Greeter {
greeting: String,
greetee: Greetee,
end: String,
}
struct Greetee {
title: Option<String>,
greetee: String,
}
unsafe impl<S> PlaceDrop<S> for Box<S::Source>
where
S: Subplace,
{
/* ... */
}
unsafe impl<T: ?Sized> DropHusk for Box<T> { /* this is the normal drop impl of `Box`! */ }
fn main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: Greetee {
title: Some(String::from("awesome")),
greetee: String::from("world"),
},
end: String::from("!"),
});
let _greeting: String = greeter.greeting;
// now the place `*greeter` is partially moved out.
// To make things more interesting, let's conditionally move out of a sub-sub-place
if random() {
let _title: String = greeter.greetee.title;
}
// now `greeter` is dropped here & but before all active subplaces are dropped.
}
// also with "normal" drop-elaboration (I'm just using `drop` for this)
fn desugared_main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: Greetee {
title: Some(String::from("awesome")),
greetee: String::from("world"),
},
end: String::from("!"),
});
let _greeting: String = unsafe {
<Box<Greeter> as PlaceRead<proj!(greeter.greeting)>>::read(&raw const greeter)
};
let mut dropflag#title = true;
if random() {
let _title: String = unsafe {
<Box<Greeter> as PlaceRead<proj!(greeter.greetee.title)>>::read(&raw const greeter)
};
dropflag#title = false;
drop(_title);
}
drop(_greeting);
if dropflag#title {
// we can drop the whole thing, since if we didn't hit `random()`, then
// `greetee` is still initialized.
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.greetee)>>::drop(&raw const greeter);
}
} else {
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.greetee.greetee)>>::drop(&raw const greeter);
}
}
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.end)>>::drop(&raw const greeter);
}
unsafe { <Box<Greeter> as DropHusk>::drop_husk(&raw const greeter) };
}| /// - field accesses, | ||
| /// - array/slice indexes. | ||
| /// | ||
| /// This represents an arbitrary chaining of these; it can also be empty. |
There was a problem hiding this comment.
"emptiness" here is not really something the user can observe, at least without additional knowledge, right? Because Source == Target is not enough for ?Sized types (for Sized it is I think enough).
There was a problem hiding this comment.
That is correct. I added this part to make people aware of the fact that a subplace doesn't have to be a strict subplace, but can also just be the entire original place.
This comes up a lot in practice, for example writing let x = @*y; will invoke PlaceBorrow with FullSubplace.
| fn offset( | ||
| self, | ||
| metadata: <Self::Source as Pointee>::Metadata, | ||
| ) -> (usize, <Self::Target as Pointee>::Metadata); |
There was a problem hiding this comment.
Would it be accurate to think of this as being implemented for (impl Field, impl Field, impl Field, ...)? I think the answer is 'not quite' because array indexing is not Field'able, since FRTs are always ZSTs? But maybe Field should be generalized such that they can contain arbitrary metadata (I guess that's what Subplace is)?
Semi-related, how does this work if I have foo[idx] where idx: !Copy? That seems to rule out desugaring to Subplace, since Subplace: Copy. Do we expect that desugaring? Is the intent that array indexing doesn't actually go through place-traits?
There was a problem hiding this comment.
Kinda yes, but FRTs can only contain static information, so unsized fields cannot be represented by them. Then as you already mentioned array and slice indexing is also dynamic, so we need primitive subplaces for them. Yes again to the statement that Subplace is a generalization of Field. Two more notes on this: 1) this is the reason why I think that we don't need FRTs for field projection at the moment, we have a more general feature that we already need. 2) the compiler-generated types that implement Subplace will not be #[fundamental] because of the orphan rule issues I mentioned in the other PR. So one cannot store special information on subplaces like one is able to with FRTs on fields.
I imagine to just have this lang item similar to FRTs that will be the actual type used behind the scenes:
pub struct SubplaceChain<S1, S2>(S1, S2);
unsafe impl<S1, S2> Subplace for SubplaceChain<S1, S2>
where
S1: Subplace,
S2: Subplace<Source = S1::Target>,
{
type Source = S1::Source;
type Target = S2::Target;
fn offset(self, meta: <S1::Source as Pointee>::Metadata) -> (usize, <S2::Target as Pointee>::Metadata) {
let (off1, meta) = self.0.offset(meta);
let (off2, meta) = self.1.offset(meta);
(off1 + off2, meta)
}
}We then also have one for the FullSubplace as I gave in the other thread. We then probably also want some for array and slice indexing and then finally implement Subplace for FRTs.
Regarding !Copy indexes: that's a great observation! We need to lift that restriction then. While we don't support custom indexing at the moment, we want to keep the door open for experimentation later.
| - _0 = offset::<*const i32, isize>(move _3, move _4) -> [return: bb1, unwind unreachable]; | ||
| - _0 = std::intrinsics::offset::<*const i32, isize>(move _3, move _4) -> [return: bb1, unwind unreachable]; | ||
| + _0 = Offset(move _3, move _4); | ||
| + goto -> bb1; |
There was a problem hiding this comment.
What happened here? It doesn't seem like we're changing anything in MIR building...
There was a problem hiding this comment.
This change also confused me a lot, but I needed it to pass CI. I asked @dingxiangfei2009 about it and he said that it is due to the potential ambiguity the Subplace::offset function introduces. I don't know how the name selection algorithm in MIR works, but Xiang said that he would like it if MIR always used the fully qualified name. But that should be a different PR. Not sure if we can do anything here (IIUC).
|
Reminder, once the PR becomes ready for a review, use |
There was a problem hiding this comment.
Thanks a lot for taking a look!
We do not intend any stable-visible implications with these traits. AFAIK, we don't have any today, but I'm not too confident in that statement, as I am not familiar with what things to watch out for. Would be great to get your assessment!
I'd be happy to have a meeting!
| @@ -0,0 +1,220 @@ | |||
| use crate::field::Subplace; | |||
There was a problem hiding this comment.
That was also my impression. Any ideas for the name of the module? ops::place sounds like a very sensible idea. Alternatively, having it in core::place might also work, but then we're no longer grouping all operators under core::ops. What do you think?
| /// The type of the contained place. | ||
| #[lang = "place_target"] | ||
| type Target: ?Sized; | ||
| } |
There was a problem hiding this comment.
No, this trait will be implemented for (dumb & smart) pointers like Box, Arc, *mut, &mut, NonNull etc. It will also be implemented for place wrapper like MaybeUninit, Cell etc. Anything that conceptually "points at a place" or "contains a place" should implement this trait. Maybe the name Place is a bit misleading after all, especially because Subplace is representing an actual subplace... I'm discussing the name bikeshedding more on zulip.
|
|
||
| /// Reading a place `let val = *x;`. | ||
| /// | ||
| /// When `x: Self`, then `let val = *x;` will be desugared into [`PlaceRead::read`]. |
There was a problem hiding this comment.
The S is derived from the place expression the operation is applied to. We have a rough sketch of the algorithm, but in this simple example, the desugaring looks like this:
// Let's say the target of `X` is `Y` and that it has a blanket impl of
// `PlaceRead` for all subplaces.
let x: X = ...;
let val = *x;
// desugars to:
let val = unsafe {
<P as PlaceRead<FullSubplace<Y>>>::read(&raw const x)
};
// With `FullSubplace` defined as a ZST and this impl:
impl<T: ?Sized> Subplace for FullSubplace<T> {
type Source = T;
type Target = T;
fn offset(
self,
meta: <T as Pointee>::Metadata,
) -> (usize, <T as Pointee>::Metadata) {
(0, meta)
}
}I don't think it's a good idea to plaster this on every operation trait, we should link to a section in the docs that explains this once. Additionally, this is independent of each operation trait, as this desugaring is attached to the place expression the operation is done on.
My current plan is to implement a set of macros that do the desugaring in the compiler as the next step after the lang items. We already have a rough sketch of the desugaring algorithm. But I'd like to implement it in code before committing it to documentation properly. Would it be okay with you if I added a stub section in the module-level docs of the ops::place submodule that I link to from the traits regarding desugaring? I would fill the stub in the next PR along with the desugaring macros.
Regarding the second point: We will most likely not change the borrow checker to act on PlaceRead::read calls; instead we will likely just change MIR to have a custom read, or change the existing read operation to allow for custom code doing the actual read. So in that sense, the operator isn't actually a desugared before MIR. But I think it's good to teach it that way, since Rust users don't really know about MIR all that much.
| /// When the compiler emits the call to `read`, the borrow checker guarantees that no other | ||
| /// operation is conflicting with this one. | ||
| #[lang = "place_read_safety"] | ||
| const SAFETY: bool; |
There was a problem hiding this comment.
Your characterization is spot-on. I no longer follow my own reasoning for having that second paragraph 😅. I think what I wanted to say with that is "if this is set to true, then the safety requirements of the operation are exactly those guaranteed by the compiler when borrow-check succeeds for a program".
Is this better?
/// When the operator is used, the borrow checker follows its usual rules to ensure that no
/// other operation conflicts with this one. If that alone is sufficient to make this operation
/// sound, then this should be `true`.
I still feel like it's a bit cryptic.
As for guiding impls in std, this value should most of the time be true, so for all smart pointers like Box, Arc &mut T etc. Only pointers that don't guarantee that they are always valid will have false here like *mut T, NonNull<T> etc.
| /// When the compiler emits the call to `read`, the borrow checker guarantees that no other | ||
| /// operation is conflicting with this one. | ||
| #[lang = "place_read_safety"] | ||
| const SAFETY: bool; |
There was a problem hiding this comment.
Our current implementation plan is to only allow true or false here. Eventually, we could relax this to allow for expressions that always evaluate to a concrete value when encountering a use of the operator.
| /// When `x: Self` and one performs a [`PlaceRead::read`] where the target value is not [`Copy`], | ||
| /// then the compiler checks if this trait is implemented and if so, moves the value out by reading | ||
| /// it and adjusting the borrow checker state of the place. |
There was a problem hiding this comment.
Correct. But we're only performing the check typeof(p): PlaceMove if the pointee (S::Target) doesn't implement Copy. So we would for example not implement PlaceMove for &T, but you could still "move out" of a &u8, since u8: Copy.
| /// Dropping a place. | ||
| /// | ||
| /// Emitted by the compiler when a place has been partially moved out and the pointer with ownership | ||
| /// is being dropped. |
There was a problem hiding this comment.
Sure!
struct Greeter {
greeting: String,
greetee: String,
}
unsafe impl<S> PlaceDrop<S> for Box<S::Source>
where
S: Subplace,
{
/* ... */
}
unsafe impl<T: ?Sized> DropHusk for Box<T> { /* this is the normal drop impl of `Box`! */ }
fn main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: String::from("world"),
});
let _greeting: String = greeter.greeting;
// now the place `*greeter` is partially moved out.
// now `greeter` is dropped here & but before all active subplaces are dropped.
}
// also with "normal" drop-elaboration (I'm just using `drop` for this)
fn desugared_main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: String::from("world"),
});
let _greeting: String = unsafe {
<Box<Greeter> as PlaceRead<proj!(greeter.greeting)>>::read(&raw const greeter)
};
drop(_greeting);
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.greetee)>>::drop(&raw const greeter);
}
unsafe { <Box<Greeter> as DropHusk>::drop_husk(&raw const greeter) };
}I decided to also create a more complex example with dropflags and multiple fields:
Details
struct Greeter {
greeting: String,
greetee: Greetee,
end: String,
}
struct Greetee {
title: Option<String>,
greetee: String,
}
unsafe impl<S> PlaceDrop<S> for Box<S::Source>
where
S: Subplace,
{
/* ... */
}
unsafe impl<T: ?Sized> DropHusk for Box<T> { /* this is the normal drop impl of `Box`! */ }
fn main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: Greetee {
title: Some(String::from("awesome")),
greetee: String::from("world"),
},
end: String::from("!"),
});
let _greeting: String = greeter.greeting;
// now the place `*greeter` is partially moved out.
// To make things more interesting, let's conditionally move out of a sub-sub-place
if random() {
let _title: String = greeter.greetee.title;
}
// now `greeter` is dropped here & but before all active subplaces are dropped.
}
// also with "normal" drop-elaboration (I'm just using `drop` for this)
fn desugared_main() {
let mut greeter = Box::new(Greeter {
greeting: String::from("Hello"),
greetee: Greetee {
title: Some(String::from("awesome")),
greetee: String::from("world"),
},
end: String::from("!"),
});
let _greeting: String = unsafe {
<Box<Greeter> as PlaceRead<proj!(greeter.greeting)>>::read(&raw const greeter)
};
let mut dropflag#title = true;
if random() {
let _title: String = unsafe {
<Box<Greeter> as PlaceRead<proj!(greeter.greetee.title)>>::read(&raw const greeter)
};
dropflag#title = false;
drop(_title);
}
drop(_greeting);
if dropflag#title {
// we can drop the whole thing, since if we didn't hit `random()`, then
// `greetee` is still initialized.
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.greetee)>>::drop(&raw const greeter);
}
} else {
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.greetee.greetee)>>::drop(&raw const greeter);
}
}
unsafe {
<Box<Greeter> as PlaceDrop<proj!(greeter.end)>>::drop(&raw const greeter);
}
unsafe { <Box<Greeter> as DropHusk>::drop_husk(&raw const greeter) };
}| /// - field accesses, | ||
| /// - array/slice indexes. | ||
| /// | ||
| /// This represents an arbitrary chaining of these; it can also be empty. |
There was a problem hiding this comment.
That is correct. I added this part to make people aware of the fact that a subplace doesn't have to be a strict subplace, but can also just be the entire original place.
This comes up a lot in practice, for example writing let x = @*y; will invoke PlaceBorrow with FullSubplace.
| fn offset( | ||
| self, | ||
| metadata: <Self::Source as Pointee>::Metadata, | ||
| ) -> (usize, <Self::Target as Pointee>::Metadata); |
There was a problem hiding this comment.
Kinda yes, but FRTs can only contain static information, so unsized fields cannot be represented by them. Then as you already mentioned array and slice indexing is also dynamic, so we need primitive subplaces for them. Yes again to the statement that Subplace is a generalization of Field. Two more notes on this: 1) this is the reason why I think that we don't need FRTs for field projection at the moment, we have a more general feature that we already need. 2) the compiler-generated types that implement Subplace will not be #[fundamental] because of the orphan rule issues I mentioned in the other PR. So one cannot store special information on subplaces like one is able to with FRTs on fields.
I imagine to just have this lang item similar to FRTs that will be the actual type used behind the scenes:
pub struct SubplaceChain<S1, S2>(S1, S2);
unsafe impl<S1, S2> Subplace for SubplaceChain<S1, S2>
where
S1: Subplace,
S2: Subplace<Source = S1::Target>,
{
type Source = S1::Source;
type Target = S2::Target;
fn offset(self, meta: <S1::Source as Pointee>::Metadata) -> (usize, <S2::Target as Pointee>::Metadata) {
let (off1, meta) = self.0.offset(meta);
let (off2, meta) = self.1.offset(meta);
(off1 + off2, meta)
}
}We then also have one for the FullSubplace as I gave in the other thread. We then probably also want some for array and slice indexing and then finally implement Subplace for FRTs.
Regarding !Copy indexes: that's a great observation! We need to lift that restriction then. While we don't support custom indexing at the moment, we want to keep the door open for experimentation later.
| - _0 = offset::<*const i32, isize>(move _3, move _4) -> [return: bb1, unwind unreachable]; | ||
| - _0 = std::intrinsics::offset::<*const i32, isize>(move _3, move _4) -> [return: bb1, unwind unreachable]; | ||
| + _0 = Offset(move _3, move _4); | ||
| + goto -> bb1; |
There was a problem hiding this comment.
This change also confused me a lot, but I needed it to pass CI. I asked @dingxiangfei2009 about it and he said that it is due to the potential ambiguity the Subplace::offset function introduces. I don't know how the name selection algorithm in MIR works, but Xiang said that he would like it if MIR always used the fully qualified name. But that should be a different PR. Not sure if we can do anything here (IIUC).
This comment has been minimized.
This comment has been minimized.
| #[rustc_deny_explicit_impl] | ||
| #[rustc_dyn_incompatible_trait] | ||
| #[lang = "subplace"] | ||
| pub unsafe trait Subplace: Sized + Copy { |
There was a problem hiding this comment.
I put this trait here in field.rs, since I didn't have a better module in mind. It isn't an operator trait, so ops felt very wrong. Now that I'm adding the ops::place module, I think it could be fine to add it there, we would break the rule of "only ops in ops", but it's a submodule and the Subplace trait really only is interesting there.
Another place where it could go is core::marker.
Thoughts?
6e48289 to
f5cbf5d
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
Arg, I accidentally pushed some local changes that I wanted to put on top of the rebase... Here is the actual range-diff |
This comment has been minimized.
This comment has been minimized.
|
@rustbot ready |
|
|
||
| use crate::ptr::Pointee; | ||
|
|
||
| /// A subplace of [`Self::Source`] with the type [`Self::Target`]. |
There was a problem hiding this comment.
Is it true that types implementing this trait are always created by the compiler? If so it would help to clarify that. Otherwise, I'd add that the compiler does create some special types that implement this trait.
There was a problem hiding this comment.
Hmm I don't think we have that figured out yet. I think that we could get away with just having some lang items that implement this trait & no user-impls. But we want to keep it open for custom subplaces (for example the one of HashMap).
|
|
||
| /// Forwards the subplace `S` of the place contained by this. | ||
| /// | ||
| /// When `x: Self` and `Self::Target` has a subplace `S` accessible via |
There was a problem hiding this comment.
One thing I'm unclear about: How does the Wrapped type get created?
Below there's a test that uses an actual wrapper type implementing Subplace, but I thought that was not going to be allowed (and the test includes errors for that).
There was a problem hiding this comment.
This is essentially the same answer as the other thread: #154940 (comment). i.e. we either allow implementing it, or have a few lang items that users would use instead.
(I created the test for the feature gate errors of implementing it, we should probably have it regardless of being allowed to implement it or not)
|
I just pushed the hopefully final trait rename of this PR. I'm very happy with the current names & the story they are telling. They are of course temporary until we accept an RFC or until we stabilize the traits. Let me know if I should squash the commits, I didn't force push to help with commit-based reviews. |
View all comments
@oli-obk I'll be assigning Mark to review this, since this is exclusively library changes.
r? @Mark-Simulacrum
opsmodule, maybe we should have aops::placesubmodule and put all the things there?