-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Closure returned as impl FnOnce
incorrectly borrows captured used-by-value iff it is Copy
#100905
Comments
I suspect this is due to how closures "sufficiently" capture the captured variables; for a However, this behavior is imho nondesirable, as it makes the semantics visibly (because of lifetimes) change to treat the type differently because it is known to be |
This doesn't just happens when returning the closure, I encountered this with the new scoped thread API. pub fn test(n: usize) {
let sum = Mutex::new(0);
std::thread::scope(|s| {
for i in 0..n {
s.spawn(|| {
*sum.lock().unwrap() += i;
});
}
});
} This doesn't compile because "closure may outlive the current function, but it borrows If I change the code to use another type for struct W(usize);
impl AddAssign<W> for usize {
fn add_assign(&mut self, rhs: W) {
*self += rhs.0;
}
}
pub fn test2(n: usize) {
let sum = Mutex::new(0);
std::thread::scope(|s| {
for i in (0..n).map(W) {
s.spawn(|| {
*sum.lock().unwrap() += i;
});
}
});
} Of course adding |
👋 Hi, maintainer of Related: obi1kenobi/cargo-semver-checks#5 |
The behavior is "by design", for better or worse. Without the With the I don't believe we did a thorough write-up of the reasoning here -- though I have a vague memory of an internals thread -- but it helps avoid some surprising behavior when combined with let mut i = 0;
let c = || i;
i += 1;
println!("{}", c()); but if you add the The intent was that plain |
Given all this, though, what's the fate of this bug? The system is working as designed. It'd be good to verify that this is documented in the reference (I think I've had "Document capture rules better" on my to-do list for a while, actually, now that I think about it...), and it's good to document this in terms of the our semver recommendations, but changing the language is more like "RFC territory" (I assume it'd be backwards incompatible, though I don't have an example off hand I suppose, maybe that's not the case). |
If non- I believe changing this would be nonbreaking, because the only ways to observe the difference between by-ref and by-copy captures are
I accept that this is more in the territory of a language change than a bugfix, but I've more often seen closure captures explained as "by example" than "as needed" (even when the words "as needed" are used) and never once seen this corner case mentioned. I'm pretty sure if you asked a lot of programmers whether It seems painful to say you have to spell out every capture you do want to capture by reference in a pseudo captures list in order to capture To note, |
@rustbot labels +AsyncAwait-Triaged +WG-async We reviewed this today in WG-async triage. As @nikomatsakis said here, this behavior is by design, for better or worse. While we'd like to see better documentation about this, we can probably go ahead and close this particular issue. |
Given the following code: [playground] (with the other cases as well)
The current output is:
If you remove the
Copy
implementation, then this compiles without error.APIT also compiles without error, but TAIT also causes an error.
Without
Copy
APIT
TAIT
I checked, and this has been the case since RPIT was first stabilized.
@rustbot label +A-impl-trait
Technically, this makes adding
Copy
to a type a breaking change 🙃I expected to see this happen: explanation
Instead, this happened: explanation
Meta
rustc version: 1.65.0-nightly (2022-08-16 86c6ebe)
The text was updated successfully, but these errors were encountered: