Skip to content
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

Update rust reference info about closures #1059

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

roxelo
Copy link
Member

@roxelo roxelo commented Jul 6, 2021

This PR updates the closure types page so it includes up to date information on the new behavior introduced by RFC2229/Edition 2021

cc: @arora-aman
r? @nikomatsakis

Closes #1066

@ehuss ehuss added the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Jul 6, 2021
@nikomatsakis nikomatsakis self-assigned this Jul 6, 2021
Copy link
Contributor

@ehuss ehuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Just some minor editorial comments. I'll let Niko review the actual content. 😉

src/types/closure.md Outdated Show resolved Hide resolved
src/types/closure.md Outdated Show resolved Hide resolved
src/types/closure.md Outdated Show resolved Hide resolved
src/types/closure.md Outdated Show resolved Hide resolved
@roxelo roxelo force-pushed the closure-doc branch 2 times, most recently from 37ce76c to bc0e50c Compare July 9, 2021 00:16
@ehuss ehuss added the A-edition-2021 Area: Edition 2021 label Jul 9, 2021
Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content is good. It'd be nice to also add in the "formal algorithm" that we came up with in our last meeting.

src/types/closure.md Show resolved Hide resolved
src/types/closure.md Show resolved Hide resolved
@@ -316,65 +316,69 @@ If a closure captures a field of a composite types such as structs, tuples, and
## Overall Capture analysis algorithm

* Input:
* Analyzing the closure C yields a set of `(Mode, Place)` pairs that are accessed
* Analyzing the closure C yields a mapping of `Place -> Mode` that are accessed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add details for how things are currently stored in the initial analysis phase

* Helper functions:
* `copy_type(Mode, Place) -> (Mode, Place)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a concern of the capture analysis anymore

src/types/closure.md Show resolved Hide resolved

struct Point { x: i32, y: i32 }
Copy link
Member

@arora-aman arora-aman Jul 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These examples were us discussiong what analysis should do in moving copy types and working through some examles

@arora-aman arora-aman force-pushed the closure-doc branch 2 times, most recently from 58a0132 to 880405c Compare July 20, 2021 09:00
src/types/closure.md Outdated Show resolved Hide resolved
Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very nice. I left some comments.

* Input:
* Analyzing the closure C yields a mapping of `Place -> Mode` that are accessed
* Access mode is `ref`, `ref uniq`, `ref mut`, or `by-value` (ordered least to max)
* For a `Place` that is used in two different acess modes within the same closure, the mode reported from closure analysis is the maximum access mode.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* For a `Place` that is used in two different acess modes within the same closure, the mode reported from closure analysis is the maximum access mode.
* For a `Place` that is used in two different access modes within the same closure, the mode reported from closure analysis is the maximum access mode.


* Input:
* Analyzing the closure C yields a mapping of `Place -> Mode` that are accessed
* Access mode is `ref`, `ref uniq`, `ref mut`, or `by-value` (ordered least to max)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still a bit unclear on the best way to incorporate ref uniq here -- my sense is that the results that come in initially should never have ref uniq, but it gets determined later, whenever we do a truncation through a * of an &mut (as we talked about).

* Let Place1 = (Base, Projections[0..=l])
* Return (Place1, Ref)

## Key examples
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are pretty inscrutable if you didn't attend our meeting, I think

src/types/closure.md Show resolved Hide resolved
src/types/closure.md Show resolved Hide resolved
src/types/closure.md Show resolved Hide resolved
src/types/closure.md Show resolved Hide resolved
src/types/closure.md Outdated Show resolved Hide resolved
@ehuss ehuss removed the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Nov 1, 2021
@ehuss
Copy link
Contributor

ehuss commented Nov 19, 2021

@roxelo / @arora-aman / @nikomatsakis Is there an update on how this is going? It looks like niko left some comments that may not have been addressed?

Co-authored-by: Josh Triplett <josh@joshtriplett.org>
@arora-aman
Copy link
Member

@roxelo / @arora-aman / @nikomatsakis Is there an update on how this is going? It looks like niko left some comments that may not have been addressed?

@ehuss I think they have been addressed, github didn't auto resolve them because lines were added instead of them being changed.

There was one style change that josh left that I have now committed. Hopefully tests pass given Edition 2021 has been stabilized

@arora-aman
Copy link
Member

arora-aman commented Nov 19, 2021

I should have time tomorrow to fix the style errors.

@roxelo
Copy link
Member Author

roxelo commented Dec 3, 2021

Just saw this now, can help if needed @arora-aman

borrowed to iterate over, the code would not compile.
### Capturing references in move contexts

Moving fields out of references is not allowed. As a result, in the case of move closures, when values accessed through a shared references are moved into the closure body, the compiler will truncate right before a dereference.
Copy link
Member

@shepmaster shepmaster Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to understand the logic behind

Moving fields out of references is not allowed

Where could I learn more? My broader context is Why does moving a disjoint field capture into a closure differ when the type is a value vs a reference?

Comment on lines +93 to +104
### Wild Card Patterns
Closures only capture data that needs to be read, which means the following closures will not capture `x`

```rust
let x = 10;
let c = || {
let _ = x;
};

let c = || match x {
_ => println!("Hello World!")
};
Copy link
Member

@Veykril Veykril May 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be wrong. Both of these examples are captured by value today -> https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4044e24fa1cffb795c2dd03239eb5fb2

Nevermind, coercion logic does not have access to semantic captures

let c_box = || {
println!("{}", (*b).0); // closure captures `b`
};
```

## Unique immutable borrows in captures
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example in this section needs to be updated, as it now captures *x by mutable borrow, not x by unique immutable borrow.

@ehuss
Copy link
Contributor

ehuss commented May 7, 2023

@Veykril or @HKalbasi Would either of you be interested in helping to get this PR over the line? Are either of you familiar with how they are implemented? I am not familiar with how closures are implemented in rustc. I could start reviewing all of the RFC 2229 PR's and reviewing all of the current implementation, but I have been dragging because that sounds like weeks of work. It would help to have someone else to ask questions to and to help with reviewing.

@HKalbasi
Copy link
Member

HKalbasi commented May 7, 2023

I guess I can help since I reimplemented this in rust-analyzer and got more or less familiar with the rustc implementation. What should I do?

@ehuss
Copy link
Contributor

ehuss commented May 7, 2023

Thanks! I'm not sure exactly how to make progress, but perhaps some questions might help to get started:

  1. Have you carefully reviewed this PR? If not, would you be willing to give it a detailed examination? Are there any details missing?
  2. Can you point me to a rough outline of the areas in rustc that I would need to look at to understand the text of this PR?
  3. How difficult do you think it would be for me to figure it out? I'm moderately familiar with rustc, but not this area (nor type inference or type checking, which I'm not sure is relevant here). I'm not sure if I'm vastly over- or under-estimating how hard this is to review.

I can try to review the comment you left and push changes where I feel confident in them, but if I end up having questions it would be helpful to have someone to ask.

Hopefully there aren't too many gray areas where it isn't clear if some behavior was intended or not.

@HKalbasi
Copy link
Member

HKalbasi commented May 7, 2023

Have you carefully reviewed this PR? If not, would you be willing to give it a detailed examination? Are there any details missing?

Not yet, but will do it in the following days when I found time.

Can you point me to a rough outline of the areas in rustc that I would need to look at to understand the text of this PR?

The whole thing happens in expr_use_visitor.rs and upvars.rs. The entry point is the analyze_closure in upvars.rs, which calls ExprUseVisitor::consume_body to collect all of the places, and then aggregates them, applies some changes and optimizations, trims the places in old editions and computes the closure kind.

How difficult do you think it would be for me to figure it out? I'm moderately familiar with rustc, but not this area (nor type inference or type checking, which I'm not sure is relevant here). I'm not sure if I'm vastly over- or under-estimating how hard this is to review.

It's pretty straightforward, specially if you are familiar with the Place concept. I think you won't have difficulties in finding the corresponding code for any part of text, but checking that if every small detail is covered might becomes a little difficult.

Comment on lines +133 to +145
### Reference into unaligned `struct`s

Because it is `unsafe` to hold references to unaligned fields in a structure, closures will only capture the prefix of the path that runs up to, but not including, the first field access into an unaligned structure.

```rust
#[repr(packed)]
struct T(String, String);

let t = T(String::from("foo"), String::from("bar"));
let c = || unsafe {
println!("{}", t.0); // closure captures t
};
```
Copy link
Contributor

@ehuss ehuss May 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be an issue with this entire section. My understanding is that it is not unsafe to take a reference to an unaligned field; it is undefined behavior, and the compiler won't even let you naively do that (indeed this example doesn't compile).

I'm thinking about rewriting this, and changing the example to something like:

#[repr(packed)]
struct Packed {
    f1: u8,
    f2: u8,
}

let mut packed = Packed { f1: 1, f2: 2 };
let c = || {
    // closure captures `packed` in its entirety as an immutable borrow
    let raw_f2 = std::ptr::addr_of!(packed.f2);
    assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
};
c();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the example is wrong and doesn't compile, but I'm not sure if your example is relevant here. I guess the addr_of! macro will always capture the whole item, since it is expanded into something like packed as usize + some_offset.

I think this example would work:

#[repr(packed)]
struct T(i32, i32);

fn main() {
    let t = T(2, 5);
    let c = || {
        t.0; // closure captures t.0 if not packed, but t if packed
        t.1;
    };
    println!("{}", core::mem::size_of_val(&c))
}

Normally, t.0 should be captured by immutable borrow (since i32 is copy) but now that t is packed and taking &t.0 is UB, the whole t will be captured and this code will print 8 instead of 16 when T is not packed.

Comment on lines +430 to +432
### Packed-field-ref-and-move

When you have a closure that both references a packed field (which is unsafe) and moves from it (which is safe) we capture the entire struct, rather than just moving the field. This is to aid in predictability, so that removing the move doesn't make the closure become unsafe:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to Reference into unaligned structs, this doesn't compile and needs to be rewritten.

@ehuss
Copy link
Contributor

ehuss commented Jul 8, 2024

I have posted #1521 with some updates to this PR, since I am unable to push directly to this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-edition-2021 Area: Edition 2021
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2021: Update for disjoint captures (RFC 2229)
9 participants