self-referential data support#156
Draft
nbdd0121 wants to merge 12 commits into
Draft
Conversation
Have `__make_init` take `__data` back as an argument. This gives the `#[pin_data]` macro expansion an opportunity to change the type when needed. Currently the type expands the same way as the previous `__ThePinData` type, but this opportunity will be used in the future to enable self-referencing type support. Remove the `Clone` and `Copy` implementation for data types which are no longer needed. Signed-off-by: Gary Guo <gary@garyguo.net>
Most users would not need to mention the generated projection struct. With the upcoming self-referential support, there needs to be an additional projection struct. Instead of give it another publicly visible name, simply make the name not publicly accessible. A heavy user user of pin projection outside kernel is for async programming. I've checked tokio codebase there is a single use case and that's for enums. Signed-off-by: Gary Guo <gary@garyguo.net>
As a first step towards adding self-referential data structures in pin-init, introduce parsing support for needed attributes. Two attributes are used to denote variance of the field, `#[covariant]` and `#[not_covariant]`. More types are covariant over lifetimes and therefore `#[covariant]` is the default if no annotations are found. `#[borrows]` attribute is used to mark what other fields that the field can borrow from, and it is also used to mark what types of borrow it is. For example, `#[borrows(foo)]` indicates that `foo` should be shared borrowed and `#[borrows(mut foo)]` borrows `foo` mutably. In absence of `#[borrows]` attribute, `#[pin_data]` would scan the type to see if there're any unbound lifetime. Only parsing is included in this commit. The attribute syntax is inspired by the `ouroboros` crate, although many different design decisions have been taken. Link: https://docs.rs/ouroboros [1] Signed-off-by: Gary Guo <gary@garyguo.net>
Fields that borrow other fields have lifetimes that are within the struct and these are not part of the struct generics. Therefore, these fields need to have their lifetime erased. A naive implementation would be to replace their lifetimes with `'static`. However, doing so is unsound for multiple reasons: * Users may directly access such field with field access syntax, and get exposed with wrong lifetime; * Auto trait implementations will cause the struct to be implementing auto traits when the type only implements the auto trait for specific lifetime. This is similar to how specialization can be unsound if specialized on lifetime. The first issue is easy to solve by simplying wrapping the field in a struct that blocks direct access. The second issue is more involved. This is where higher-ranked trait bound comes in. We can define a `ForLt` trait, and if we have a `F` where `<F as ForLt>::Of<'a>` is the user-provided type that makes use of 'a that refers to a borrowed field. Then we can use `for<'a> F::Of<'a>: Send` to constrain that the user-provided type needs to be `Send` for all lifetimes rather than a specific lifetime before the type can be proved by the trait resolver to be `Send`. The above strategies is what underpins the `SelfRef` type, which is used for all fields that borrows from the other field. The actual implementation is a bit more complex version so it can handle multiple lifetimes. Apart from `Send` and `Sync`, we also have an auto-trait `Unpin`. Obviously, self-referential structs need always be `!Unpin`. This is enforced by ensuring `SelfRef: !Unpin` and force the generated `Unpin` implementation never evaluates never satisfy its bound. Signed-off-by: Gary Guo <gary@garyguo.net>
Check drop order top ensure that usage of lifetime inside self-referential
struct is consistent with the order that the fields will dropped in drop
glue.
First, fields are checked according to their index to ensure that if `a`
borrows from `b`, `b` must outlive `a`. This is simple and produce a very
good diagnostics when misued.
Lifetime bounds can also be indirectly crafted with implied bounds that
make fields well-formed. For example, in this struct
struct Foo {
x: &'b &'a (),
a: String,
y: PrintOnDrop<&'b str>,
b: String,
}
`&'b &'a ()` will imply that `a` outlive `b`, which is inconsistent with
the actual drop order. A more sophisticated method is used to ensure that
this cannot happen.
With this change, the struct itself can now soundly exist. It cannot yet be
initialized with `pin_init!` or projected with `project()` method.
Signed-off-by: Gary Guo <gary@garyguo.net>
We now have the checks to ensure that lifetime relations are what is expected, we can generate the slot projections in `generate_pin_data` so self-referential struct can be implemented. New slot and guard types are defined (`SelfRefSlot` and `SelfRefDropGuard`) which gives the generated let bindings longer lifetime than the guard themselves. Higher-ranked trait bound on `__make_init` is used to ensure that the initialization closure cannot make arbitrary assumptions of those lifetimes. Currently, this is only implemented for shared borrows. Signed-off-by: Gary Guo <gary@garyguo.net>
This adds the projection for fields that are shared borrowed or that borrows other fields but is covariant. Both cases allow a shared reference to be accessed. No mutable references can be created for these cases for different reasons: * For fields that are shared borrowed, aliasing restriction prevents creation of mutable reference * For fields that borrows other fields, their proper type contains field lifetimes. These lifetimes cannot be made available in the returned `project` struct (because there is no way to represent existential lifetime in return position). For covariant types, it is possible to shorten these lifetimes to that of `&self`; but doing so requires the reference to also be covariant over the pointee type, so we cannot give out `&mut` as it is invariant over the pointee. Due to field-referencing fields being wrapped inside `SelfRef`, the normal accessor syntax stop working; create accessor methods for these fields instead. Signed-off-by: Gary Guo <gary@garyguo.net>
The `project` method needs to perform covariant coercion on covariant fields, causing them to no longer being mutable. Implement a `with_project` that does not require covariant coercion by using higher-ranked trait bounds, thus allow the fields to be assignable inside the callback. This mechanism can also be used to access non-covariant fields. Signed-off-by: Gary Guo <gary@garyguo.net>
Allow fields to be mutably referenced by other fields in addition to shared references. In order for this to be sound, the fields that can be mutably borrowed are blocked from being accessed via field access syntax or projection to maintain the aliasing requirements. Signed-off-by: Gary Guo <gary@garyguo.net>
With the previous patch, non-covariant types can already be accessed inside
projections. As projections are only generated for `Pin<&mut T>`, they're
not accessible otherwise. Add `with_{field_name}` methods so fields can be
accessed using closures with just `&T`.
Signed-off-by: Gary Guo <gary@garyguo.net>
Currently lifetimes are replaced with `ForLt4` trait. This is very general approach as it uses generic associated type to replace lifetime, so it can even work when macros are involved. This does cause more generated code, and does not render in documentation nicely. Thus, just replace the lifetime in the AST if no macros are involved. Signed-off-by: Gary Guo <gary@garyguo.net>
Add the outlive relations per field drop order. This allows a single lifetime to be used when a field potentially borrow from two different fields, by allowing the longer-living field lifetime to be shortened to a shorter-living field lifetime. Signed-off-by: Gary Guo <gary@garyguo.net>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds self-referential data support, making this possible with safe code: