خطأ: لا يمكن الخروج من المتغير الخارجي الملتقط في إغلاق
FnMut
struct Foo {
a: i32,
b: i32,
bar: Bar,
}
struct Bar;
impl Bar {
fn f<F: FnMut()>(&self, mut f: F) {
f();
}
}
fn main() {
let mut foo = Foo { a: 1, b: 2, bar: Bar };
let a = &mut foo.a;
let b = &mut foo.b;
(|| { // works
*if true {a} else {b} = 42;
})();
let mut foo = Foo { a: 1, b: 2, bar: Bar };
let a = &mut foo.a;
let b = &mut foo.b;
foo.bar.f(|| { // doesn't work
*if true {a} else {b} = 42;
});
}
https://play.rust-lang.org/؟gist=4ce6948a92c2fcb281b3cade8574691d&version=nightly
لكن الحالة الثانية يجب أن تعمل أيضًا!
يعمل إذا قمت بتغيير FnMut إلى FnOnce.
ولكن لماذا لا تعمل أيضًا مع FnMut؟
(راجع للشغل ، يجب أن يشير الخطأ إلى أنه يجب أن يكون FnOnce بعد ذلك.)
Boscop لأنه يمكنك فقط نقل الأشياء مرة واحدة - يمكن استدعاء إغلاق FnMut
أكثر من مرة.
(راجع للشغل ، يجب أن يشير الخطأ إلى أنه يجب أن يكون FnOnce بعد ذلك.)
لا أوافق تمامًا ، لكنني أيضًا لا أعارض . غالبًا ما يكون من الصعب تحديد كيفية تشخيص هذا النوع من الخطأ. يوجد نوع من التوتر هنا: يريد الإغلاق اتخاذ إجراء (حركة) ، لكن توقيع f
لا يسمح بذلك (لأنه يتطلب إغلاقًا يمكن التذرع به أكثر من مرة). قد يكون هذا التوقيع خاطئًا (يجب أن يتم تعميمه لقبول FnOnce
) أو أن الإغلاق خاطئ (يحاول فعل أشياء لا ينبغي أن يفعلها). على الأقل ، من الواضح أننا لا نقوم بعمل جيد لالتقاط هذا "التوتر" في التشخيص.
ومن المثير للاهتمام ، إذا لم يتم تقديم الإغلاق مباشرة كوسيطة لـ f
فإننا نعطي خطأ أفضل إلى حد ما :
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> src/main.rs:19:19
|
19 | let closure = || { // doesn't work
| ___________________^
20 | | *if true {a} else {b} = 42;
21 | | };
| |_____^
22 | foo.bar.f(closure);
| - the requirement to implement `FnMut` derives from here
|
note: closure is `FnOnce` because it moves the variable `a` out of its environment
--> src/main.rs:20:19
|
20 | *if true {a} else {b} = 42;
|
هذا بسبب التتبع الذي وضعهestebank (على ما أظن؟ أم أنه شخص آخر ...) لتتبع سبب تحديد "السمة المحددة" للإغلاق. في الوقت الحالي ، لم يتم تفعيل هذا الفحص لأننا قررنا أن الإغلاق يجب أن يكون FnMut
فقط بسبب توقيع f
.
يحدث ذلك في هذا الكود:
عندما نسير في هذا الطريق ، لا ينتهي بنا المطاف بتخزين "أصل" هذا النوع. على النقيض من ذلك ، فإن الاستدلال الصعودي الذي كان سيبدأ لولا ذلك يفعل هذا:
لذلك ربما نحتاج إلى تخزين نوع من المعلومات في closure_kind_origins_mut
ثم تحسين شيك الاقتراض. ثم يمكننا تقديم خطأ مثل:
error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
--> src/main.rs:20:19
|
17 | let a = &mut foo.a;
| - captured outer variable
...
20 | *if true {a} else {b} = 42;
| ^ cannot move out of captured outer variable in an `FnMut` closure
note: closure is `FnMut` because of the requirements of `f()`
--> src/main.rs:19:19
|
19 | foo.bar.f(||
| ^^^^^^^^^
|
التعليق الأكثر فائدة
Boscop لأنه يمكنك فقط نقل الأشياء مرة واحدة - يمكن استدعاء إغلاق
FnMut
أكثر من مرة.لا أوافق تمامًا ، لكنني أيضًا لا أعارض . غالبًا ما يكون من الصعب تحديد كيفية تشخيص هذا النوع من الخطأ. يوجد نوع من التوتر هنا: يريد الإغلاق اتخاذ إجراء (حركة) ، لكن توقيع
f
لا يسمح بذلك (لأنه يتطلب إغلاقًا يمكن التذرع به أكثر من مرة). قد يكون هذا التوقيع خاطئًا (يجب أن يتم تعميمه لقبولFnOnce
) أو أن الإغلاق خاطئ (يحاول فعل أشياء لا ينبغي أن يفعلها). على الأقل ، من الواضح أننا لا نقوم بعمل جيد لالتقاط هذا "التوتر" في التشخيص.ومن المثير للاهتمام ، إذا لم يتم تقديم الإغلاق مباشرة كوسيطة لـ
f
فإننا نعطي خطأ أفضل إلى حد ما :هذا بسبب التتبع الذي وضعهestebank (على ما أظن؟ أم أنه شخص آخر ...) لتتبع سبب تحديد "السمة المحددة" للإغلاق. في الوقت الحالي ، لم يتم تفعيل هذا الفحص لأننا قررنا أن الإغلاق يجب أن يكون
FnMut
فقط بسبب توقيعf
.يحدث ذلك في هذا الكود:
https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/closure.rs#L151 -L155
عندما نسير في هذا الطريق ، لا ينتهي بنا المطاف بتخزين "أصل" هذا النوع. على النقيض من ذلك ، فإن الاستدلال الصعودي الذي كان سيبدأ لولا ذلك يفعل هذا:
https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/upvar.rs#L182 -L188
لذلك ربما نحتاج إلى تخزين نوع من المعلومات في
closure_kind_origins_mut
ثم تحسين شيك الاقتراض. ثم يمكننا تقديم خطأ مثل: