Skip to content

Commit d6d5553

Browse files
bors[bot]Aaron1011
andcommitted
Merge #18
18: Replace `unsafe_project` with safe `pin_projectable` attribute r=taiki-e a=Aaron1011 This PR replaces the `unsafe_project` attribute with a new, completely safe `pin_projectable` attribute. This attribtute enforces all of the guaranteees of pin projection: * `#[repr(packed)]` types are disallowed completely, via a compile-time error. * A `Drop` impl is unconditionally provided. A custom drop function can be provided by annotating a function with `#[pinned_drop]`. This function takes a `Pin<&mut MyType>`, which ensures that the user cannot move out of pinned fields in safe code. * An `Unpin` impl is unconditionally provided. By default, this generates the same bounds as the (now removed) `Unpin` argument - all pin-projected fields are required to implement `Unpin`. A manual impl can be provided via the `unsafe_Unpin` attribute, and a impl of the new `unsafe` trait `UnsafeUnpin`. This is a significant, non-backwards-compatible refactor of this crate. However, I believe it provides several significant benefits: * Pin projection is now *a safe operation*. In the vast majority of cases, consumers of this crate can create pin projections without a single use of `unsafe`. This reduces the number of `unsafe` blocks that must be audited, and increases confidence that a crate does not trigger undefined behavior. * The expressive power of the `#[unsafe_project]` and `#[pin_projectable]` is the same. Any code written using `#[unsafe_project]` can be adapted to `#[pin_projectable]`, even if relied in the `unsafe` nature of `#[unsafe_project]`: * `UnsafeUnpin` can be used to obtain complete control over the generated `Unpin` impl. * `Pin::get_unchecked_mut` can be used within a `#[pinned_drop]` function to obtain a `&mut MyStruct`, effectively turing `#[pinned_drop]` back into a regular `Drop` impl. * For `#[repr(packed)]` structs, there are two possible cases: * Pin projection is never used - no fields have the `#[pin]` attribute, or `project()` is never called on the base struct. In this case, using this crate for the struct is completely pointless, and the `#[unsafe_project]` attribute can be removed. * Pin projection *is* used. This is immediate undefined behavior - the new `#[pin_projectable`]` attribute is simply not allowing you to write broken code. * Anything with the potential for undefined behavior now requires usage of the `unsafe` keyword, whearas the previous `#[unsafe_project]` attribute only required typing the word 'unsafe' in an argument. Using the actual `unsafe` keyword allows for proper integration with the `unsafe_code` lint, and tools [cargo geiger](https://github.com/anderejd/cargo-geiger). Note that the `unsafe_Unpin` argument still obeys this rule - the `UnsafeUnpin` trait it enables is unsafe, and failing to provide an impl of `UnsafeUnpin` is completely safe. Unfortunately, this PR requires `pin-project` to be split into two crates - `pin-project` and `pin-project-internal`. This is due to the fact that `proc-macro` crates cannot currently export anything other than proc macros. The crates are split as follows: * A new `pin-project-internal` crates provides almost all of the functionality of this crate, with the exception of the `UnsafeUnpin` trait. * The `pin-project` crate now re-exports everything from `pin-project-internal`, with added doc comments. It also provides the `UnsafeUnpin` trait. Because the `pin-project-internal` crate must reference the `UnsafeUnpin` trait from `pin-project`, `pin-project-internal` implicitly depends on `pin-project`. To ensure that users can rename their dependency on `pin-project`, the crate [proc_macro_crate](https://crates.io/crates/proc_macro_crate) is used to dynamically determine the name of the `pin-project` crate at macro invocation time. Due to several issues with Rustdoc's handling of procedural macros, the documentation for the `pin-project` crate will not currently render properly - however, all doctests will still run correctly. Once rust-lang/rust#62855 and rust-lang/rust#63048 are merged, the documentation will build correctly on nightly Rust. @taiki-e: I'm happy to work with you to make any adjustments necessary to get this merged (once the referenced rustc PRs are merged). Co-authored-by: Aaron Hill <[email protected]>
2 parents aaa486c + 60e4242 commit d6d5553

27 files changed

+1159
-561
lines changed

Cargo.toml

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,6 @@
1-
[package]
2-
name = "pin-project"
3-
# NB: When modifying, also modify html_root_url in lib.rs
4-
version = "0.3.4"
5-
authors = ["Taiki Endo <[email protected]>"]
6-
edition = "2018"
7-
license = "Apache-2.0/MIT"
8-
description = "An attribute that creates a projection struct covering all the fields."
9-
repository = "https://github.com/taiki-e/pin-project"
10-
documentation = "https://docs.rs/pin-project/"
11-
readme = "README.md"
12-
keywords = ["pin", "macros", "attribute"]
13-
categories = ["rust-patterns"]
14-
exclude = ["/.travis.yml", "/bors.toml"]
1+
[workspace]
152

16-
[badges]
17-
travis-ci = { repository = "taiki-e/pin-project" }
18-
19-
[lib]
20-
proc-macro = true
21-
22-
[features]
23-
# Default features.
24-
default = ["project_attr"]
25-
# Enable to use `project` attribute.
26-
project_attr = ["syn/visit-mut"]
27-
28-
[dependencies]
29-
proc-macro2 = "0.4.13"
30-
quote = "0.6.8"
31-
syn = { version = "0.15.29", features = ["full"] }
32-
33-
[dev-dependencies]
34-
compiletest = { version = "0.3.21", package = "compiletest_rs", features = ["stable", "tmp"] }
3+
members = [
4+
"pin-project",
5+
"pin-project-internal",
6+
]

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ The current version of pin-project requires Rust 1.33 or later.
3434

3535
## Examples
3636

37-
[`unsafe_project`] attribute creates a projection struct covering all the fields.
37+
[`pin_projectable`] attribute creates a projection struct covering all the fields.
3838

3939
```rust
40-
use pin_project::unsafe_project;
40+
use pin_project::pin_projectable;
4141
use std::pin::Pin;
4242

43-
#[unsafe_project(Unpin)] // `(Unpin)` is optional (create the appropriate conditional Unpin implementation)
43+
#[pin_projectable]
4444
struct Foo<T, U> {
4545
#[pin]
4646
future: T,
@@ -61,7 +61,7 @@ impl<T, U> Foo<T, U> {
6161

6262
[Code like this will be generated](doc/struct-example-1.md)
6363

64-
[`unsafe_project`]: https://docs.rs/pin-project/0.3/pin_project/attr.unsafe_project.html
64+
[`pin_projectable`]: https://docs.rs/pin-project/0.3/pin_project/attr.pin_projectable.html
6565

6666
## License
6767

pin-project-internal/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[package]
2+
name = "pin-project-internal"
3+
# NB: When modifying, also modify html_root_url in lib.rs
4+
version = "0.3.4"
5+
authors = ["Taiki Endo <[email protected]>"]
6+
edition = "2018"
7+
license = "Apache-2.0/MIT"
8+
description = "An interal crate to support pin_project - do not use directly"
9+
repository = "https://github.com/taiki-e/pin-project"
10+
documentation = "https://docs.rs/pin-project/"
11+
readme = "README.md"
12+
keywords = ["pin", "macros", "attribute"]
13+
categories = ["rust-patterns"]
14+
exclude = ["/.travis.yml", "/bors.toml"]
15+
16+
[lib]
17+
proc-macro = true
18+
19+
[features]
20+
# Default features.
21+
default = ["project_attr"]
22+
# Enable to use `project` attribute.
23+
project_attr = ["syn/visit-mut"]
24+
# Enable to allow using the crate with a renamed 'pin-project' dependency
25+
renamed = ["proc-macro-crate", "serde", "lazy_static"]
26+
27+
28+
[dependencies]
29+
proc-macro2 = "0.4.13"
30+
quote = "0.6.8"
31+
syn = { version = "0.15.29", features = ["full", "extra-traits"] }
32+
proc-macro-crate = { version = "0.1.4", optional = true }
33+
# Required until a new toml-rs release is made with https://github.com/alexcrichton/toml-rs/pull/311,
34+
# and proc-macro-crate updates to that new version of toml-rs.
35+
serde = { version = "1.0.97", optional = true }
36+
lazy_static = { version = "1.3.0", optional = true }
File renamed without changes.

pin-project-internal/src/lib.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#![recursion_limit = "256"]
2+
#![doc(html_root_url = "https://docs.rs/pin-project/0.3.3")]
3+
#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))]
4+
#![warn(unsafe_code)]
5+
#![warn(rust_2018_idioms, unreachable_pub)]
6+
#![warn(single_use_lifetimes)]
7+
#![warn(clippy::all, clippy::pedantic)]
8+
#![warn(clippy::nursery)]
9+
10+
extern crate proc_macro;
11+
12+
#[macro_use]
13+
mod utils;
14+
15+
mod pin_projectable;
16+
#[cfg(feature = "project_attr")]
17+
mod project;
18+
19+
use proc_macro::TokenStream;
20+
21+
#[cfg(feature = "project_attr")]
22+
#[proc_macro_attribute]
23+
pub fn project(args: TokenStream, input: TokenStream) -> TokenStream {
24+
assert!(args.is_empty());
25+
TokenStream::from(project::attribute(input.into()))
26+
}
27+
28+
/// This is a doc comment from the defining crate!
29+
#[proc_macro]
30+
pub fn pin_project(input: TokenStream) -> TokenStream {
31+
TokenStream::from(
32+
pin_projectable::pin_project(input.into()).unwrap_or_else(|e| e.to_compile_error()),
33+
)
34+
}
35+
36+
#[proc_macro_attribute]
37+
pub fn pin_projectable(args: TokenStream, input: TokenStream) -> TokenStream {
38+
TokenStream::from(
39+
pin_projectable::attribute(args.into(), input.into())
40+
.unwrap_or_else(|e| e.to_compile_error()),
41+
)
42+
}
43+
44+
#[cfg(feature = "renamed")]
45+
lazy_static::lazy_static! {
46+
pub(crate) static ref PIN_PROJECT_CRATE: String = {
47+
proc_macro_crate::crate_name("pin-project")
48+
.expect("pin-project-internal was used without pin-project!")
49+
};
50+
}

src/unsafe_project/enums.rs renamed to pin-project-internal/src/pin_projectable/enums.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ use crate::utils::{proj_ident, VecExt};
66

77
use super::*;
88

9-
pub(super) fn parse(args: TokenStream, mut item: ItemEnum) -> Result<TokenStream> {
9+
pub(super) fn parse(
10+
args: TokenStream,
11+
mut item: ItemEnum,
12+
pinned_drop: Option<ItemFn>,
13+
) -> Result<TokenStream> {
14+
let impl_drop = ImplDrop::new(item.generics.clone(), pinned_drop)?;
1015
let mut impl_unpin = ImplUnpin::new(args, &item.generics)?;
1116

1217
if item.variants.is_empty() {
@@ -31,6 +36,7 @@ pub(super) fn parse(args: TokenStream, mut item: ItemEnum) -> Result<TokenStream
3136
enum #proj_ident #proj_generics #where_clause #proj_item_body
3237
};
3338

39+
proj_items.extend(impl_drop.build(ident));
3440
proj_items.extend(impl_unpin.build(ident));
3541
proj_items.extend(quote! {
3642
impl #impl_generics #ident #ty_generics #where_clause {

0 commit comments

Comments
 (0)