เพราะเหตุใด `std :: mem :: drop` จึงเหมือนกับการปิด | _ | () ในขอบเขตที่สูงกว่า


13

การใช้งานของstd::mem::dropเอกสารจะเป็นดังต่อไปนี้:

pub fn drop<T>(_x: T) { }

ดังนั้นฉันจึงคาดหวังว่าการปิด|_| ()(หรือที่รู้จักกันในชื่อการปิดห้องน้ำ ) จะเป็น 1: 1 ที่มีศักยภาพในการทดแทนdropทั้งสองทิศทาง อย่างไรก็ตามรหัสด้านล่างแสดงให้เห็นว่าdropไม่เข้ากันกับลักษณะการจัดอันดับที่สูงกว่าที่ถูกผูกไว้กับพารามิเตอร์ของฟังก์ชั่นในขณะที่การปิดห้องน้ำเป็น

fn foo<F, T>(f: F, x: T)
where
    for<'a> F: FnOnce(&'a T),
{
    dbg!(f(&x));
}

fn main() {
    foo(|_| (), "toilet closure"); // this compiles
    foo(drop, "drop"); // this does not!
}

ข้อความแสดงข้อผิดพลาดของคอมไพเลอร์:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^
   |     |
   |     expected signature of `for<'a> fn(&'a _) -> _`
   |     found signature of `fn(_) -> _`

error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^ expected bound lifetime parameter 'a, found concrete lifetime

พิจารณาว่าdropเป็นที่คาดคะเนทั่วไปที่เกี่ยวกับขนาดใดTก็เสียงไม่มีเหตุผลที่ว่า "ทั่วไปมากขึ้น" ลายเซ็นกันไม่ได้กับfn(_) -> _ for<'a> fn (&'a _) -> _ทำไมคอมไพเลอร์ไม่ยอมรับลายเซ็นของdropที่นี่และอะไรทำให้แตกต่างเมื่อการปิดห้องน้ำถูกแทนที่?

คำตอบ:


4

แก่นแท้ของปัญหาคือdropไม่ได้เป็นฟังก์ชั่นเดียว แต่เป็นชุดของฟังก์ชั่นที่กำหนดพารามิเตอร์ซึ่งแต่ละประเภทจะลดลง เพื่อให้เป็นไปตามลักษณะที่ถูกจัดอันดับที่สูงขึ้น (ต่อจากนี้ไป hrtb) คุณจะต้องมีฟังก์ชันเดียวที่สามารถใช้อ้างอิงกับประเภทพร้อมกันตลอดอายุการใช้งานที่กำหนด


เราจะใช้dropเป็นตัวอย่างทั่วไปของฟังก์ชั่นทั่วไป แต่ทั้งหมดนี้ใช้โดยทั่วไปเช่นกัน fn drop<T>(_: T) {}นี่คือรหัสสำหรับการอ้างอิง:

แนวคิดdropไม่ได้เป็นฟังก์ชันเดียว Tแต่หนึ่งฟังก์ชั่นสำหรับประเภทเป็นไปได้ทุก อินสแตนซ์เฉพาะของการdropใช้อาร์กิวเมนต์ของประเภทเดียว นี้เรียกว่าmonomorphization หากมีการTใช้dropที่แตกต่างกันdropจะมีการรวบรวมเวอร์ชันที่ต่างกัน นั่นเป็นเหตุผลที่คุณไม่สามารถส่งผ่านฟังก์ชั่นทั่วไปเป็นอาร์กิวเมนต์และใช้ฟังก์ชั่นนั้นในแบบทั่วไป (ดูคำถามนี้ )

บนมืออื่น ๆ , ฟังก์ชั่นเช่นfn pass(x: &i32) -> &i32 {x}ความพึงพอใจ for<'a> Fn(&'a i32) -> &'a i32hrtb ซึ่งแตกต่างจากdropเรามีซิงเกิ้ลฟังก์ชั่นที่พร้อมตอบสนองความต้องการFn(&'a i32) -> &'a i32สำหรับทุก'aอายุการใช้งาน สิ่งนี้สะท้อนให้เห็นในวิธีการpassใช้งาน

fn pass(x: &i32) -> &i32 {
    x
}

fn two_uses<F>(f: F)
where
    for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
                                       // F: Fn(&i32) -> &i32 due to lifetime elision rules.
                                       // That applies to your original example too.
{
    {
        // x has some lifetime 'a
        let x = &22;
        println!("{}", f(x));
        // 'a ends around here
    }
    {
        // y has some lifetime 'b
        let y = &23;
        println!("{}", f(y));
        // 'b ends around here
    }
    // 'a and 'b are unrelated since they have no overlap
}

fn main() {
    two_uses(pass);
}

(สนามเด็กเล่น)

ในตัวอย่างอายุการใช้งาน'aและ'bไม่มีความสัมพันธ์ซึ่งกันและกัน: ไม่ครอบคลุมกันและกันอย่างสมบูรณ์ ดังนั้นจึงไม่มีการพิมพ์ subtyping เกิดขึ้นที่นี่ มีการใช้อินสแตนซ์เดียวpassกับอายุการใช้งานที่แตกต่างกันและไม่เกี่ยวข้องกันสองช่วง

นี่คือเหตุผลที่ไม่พอใจdrop for<'a> FnOnce(&'a T)อินสแตนซ์เฉพาะใด ๆ ของdropสามารถครอบคลุมหนึ่งชีวิตเท่านั้น(ละเว้นการพิมพ์ย่อย) ถ้าเราผ่านdropเข้ามาtwo_usesจากตัวอย่างข้างต้น (ที่มีการเปลี่ยนแปลงเล็กน้อยลายเซ็นและสมมติคอมไพเลอร์ให้เรา) ก็จะต้องเลือกอายุการใช้งานโดยเฉพาะอย่างยิ่งบาง'aและตัวอย่างของdropอยู่ในขอบเขตของการtwo_usesจะเป็นFn(&'a i32)สำหรับบางคอนกรีต'aอายุการใช้งาน เนื่องจากฟังก์ชันจะใช้กับอายุการใช้งานเดียว'aเท่านั้นจึงไม่สามารถใช้กับอายุการใช้งานที่ไม่เกี่ยวข้องสองครั้งได้

แล้วทำไมการปิดห้องน้ำถึงได้รับ hrtb เมื่ออนุมานชนิดสำหรับการปิดถ้าคาดว่าคำแนะนำที่เป็นประเภทที่สูงติดอันดับลักษณะผูกพันเป็นสิ่งจำเป็นคอมไพเลอร์จะพยายามที่จะทำให้หนึ่งพอดี ในกรณีนี้มันสำเร็จ


ปัญหา # 41078เกี่ยวข้องอย่างใกล้ชิดกับสิ่งนี้และโดยเฉพาะอย่างยิ่งความคิดเห็นของ eddyb ที่นี่ให้คำอธิบายเป็นหลักข้างต้น (แม้ว่าในบริบทของการปิดมากกว่าหน้าที่ปกติ) ปัญหานี้ไม่ได้แก้ไขปัญหาปัจจุบัน มันจะกล่าวถึงสิ่งที่เกิดขึ้นหากคุณกำหนดการปิดห้องน้ำให้กับตัวแปรก่อนใช้งาน (ลองใช้!)

เป็นไปได้ว่าสถานการณ์จะเปลี่ยนไปในอนาคต แต่จะต้องมีการเปลี่ยนแปลงครั้งใหญ่ในลักษณะทั่วไปของฟังก์ชั่น monomorphized


4

ในระยะสั้นทั้งสองสายควรล้มเหลว แต่เนื่องจากหนึ่งขั้นตอนในการจัดการอายุการใช้งาน hrtb แบบเก่าคือการตรวจสอบการรั่วไหลในปัจจุบันมีปัญหาเรื่องความสมบูรณ์ของเสียงrustcยอมรับไม่ถูกต้องและยอมรับข้อผิดพลาดที่ไม่ดี

หากคุณปิดใช้งานการตรวจสอบการรั่วไหลด้วยrustc +nightly -Zno-leak-checkคุณจะเห็นข้อความแสดงข้อผิดพลาดที่สมเหตุสมผลมากขึ้น:

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(drop, "drop");
   |     ^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&'a &str,)>`
              found type `std::ops::FnOnce<(&&str,)>`

การตีความข้อผิดพลาดนี้ของฉันคือว่า&xในร่างกายของfooฟังก์ชันมีขอบเขตอายุการใช้งานที่ จำกัด กับร่างกายดังกล่าวเท่านั้นดังนั้นจึงf(&x)มีอายุการใช้งานขอบเขตที่เท่ากันซึ่งไม่สามารถตอบสนองfor<'a>ปริมาณสากลที่ต้องการได้

คำถามที่คุณนำเสนอที่นี่เกือบจะเหมือนกับ# 57642ซึ่งมีสองส่วนที่ตัดกัน

วิธีใหม่ในการกระบวนการชีวิต hrtb คือการใช้สิ่งที่เรียกว่าจักรวาล Niko มีWIPเพื่อจัดการปัญหาการรั่วไหลของจักรวาล ภายใต้ระบอบการปกครองใหม่นี้ทั้งสองส่วนของปัญหา # 57642 ที่เชื่อมโยงด้านบนได้รับการกล่าวถึงว่าล้มเหลวทั้งหมดพร้อมการวินิจฉัยที่ชัดเจนยิ่งขึ้น ฉันคิดว่าคอมไพเลอร์ควรจะสามารถจัดการโค้ดตัวอย่างของคุณได้อย่างถูกต้องด้วยเช่นกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.