การปิดอาจอยู่ได้นานกว่าฟังก์ชันปัจจุบัน

ฉันเพิ่งเริ่มเรียนรู้สนิม เพื่อจุดประสงค์นี้ ฉันกำลังเขียนโปรเจ็กต์ C++ ของฉันใหม่ใน Rust แต่ปัญหาที่ใหญ่ที่สุดคือการปิดตัวลงตลอดชีวิตและอื่นๆ

ฉันสร้างสถานการณ์จำลองขั้นต่ำสุดของปัญหา ซึ่งเห็นได้ที่นี่ และด้านล่าง:

use std::sync::Arc;
use std::cell::{RefCell, Cell};

struct Context {
    handler: RefCell<Option<Arc<Handler>>>,
}

impl Context {
    pub fn new() -> Arc<Context> {
        let context = Arc::new(Context{
            handler: RefCell::new(None),
        });

        let handler = Handler::new(context.clone());

        (*context.handler.borrow_mut()) = Some(handler);

        context
    }

    pub fn get_handler(&self) -> Arc<Handler> {
        self.handler.borrow().as_ref().unwrap().clone()
    }
}

struct Handler {
    context: Arc<Context>,

    clickables: RefCell<Vec<Arc<Clickable>>>,
}

impl Handler {
    pub fn new(context: Arc<Context>) -> Arc<Handler> {
        Arc::new(Handler{
            context: context,

            clickables: RefCell::new(Vec::new()),
        })
    }

    pub fn add_clickable(&self, clickable: Arc<Clickable>) {
        self.clickables.borrow_mut().push(clickable);
    }

    pub fn remove_clickable(&self, clickable: Arc<Clickable>) {
        // remove stuff ...
    }
}

struct Clickable {
    context: Arc<Context>,

    callback: RefCell<Option<Box<Fn()>>>,
}

impl Clickable {
    pub fn new(context: Arc<Context>) -> Arc<Clickable> {
        let clickable = Arc::new(Clickable{
            context: context.clone(),

            callback: RefCell::new(None),
        });

        context.get_handler().add_clickable(clickable.clone());

        clickable
    }

    pub fn remove(clickable: Arc<Clickable>) {
        clickable.context.get_handler().remove_clickable(clickable);
    }

    pub fn set_callback(&self, callback: Option<Box<Fn()>>) {
        (*self.callback.borrow_mut()) = callback;
    }

    pub fn click(&self) {
        match *self.callback.borrow() {
            Some(ref callback) => (callback)(),
            None => (),
        }
    }
}

struct Button {
    context: Arc<Context>,

    clickable: Arc<Clickable>,
}

impl Button {
    pub fn new(context: Arc<Context>) -> Arc<Button> {
        let clickable = Clickable::new(context.clone());

        let button = Arc::new(Button{
            context: context,

            clickable: clickable.clone(),
        });

        let tmp_callback = Box::new(|| {
            button.do_stuff();
        });
        clickable.set_callback(Some(tmp_callback));

        button
    }

    pub fn do_stuff(&self) {
        // doing crazy stuff
        let mut i = 0;

        for j in 0..100 {
            i = j*i;
        }
    }

    pub fn click(&self) {
        self.clickable.click();
    }
}

impl Drop for Button {
    fn drop(&mut self) {
        Clickable::remove(self.clickable.clone());
    }
}

fn main() {
    let context = Context::new();

    let button = Button::new(context.clone());

    button.click();
}

ฉันแค่ไม่รู้ว่าจะส่งต่อการอ้างอิงในการปิดได้อย่างไร

สิ่งที่น่าเกลียดอีกอย่างหนึ่งก็คือ Handler และ Context ของฉันต้องการกันและกัน มีวิธีที่ดีกว่าในการสร้างการพึ่งพานี้หรือไม่?


person hodasemi    schedule 17.08.2017    source แหล่งที่มา


คำตอบ (1)


กำลังปิดรหัสเริ่มต้นของคุณ

pub fn new(context: Arc<Context>) -> Arc<Button> {
    let clickable = Clickable::new(context.clone());

    let button = Arc::new(Button{
        context: context,

        clickable: clickable.clone(),
    });

    let tmp_callback = Box::new(|| {
        button.do_stuff();
    });
    clickable.set_callback(Some(tmp_callback));

    button
}

ก่อนอื่น เรามาสังเกตข้อผิดพลาดที่คุณได้รับกันก่อน

    error[E0373]: closure may outlive the current function, but it borrows `button`, which is owned by the current function
   --> src/main.rs:101:37
    |
101 |         let tmp_callback = Box::new(|| {
    |                                     ^^ may outlive borrowed value `button`
102 |             button.do_stuff();
    |             ------ `button` is borrowed here
    |
help: to force the closure to take ownership of `button` (and any other referenced variables), use the `move` keyword, as shown:
    |         let tmp_callback = Box::new(move || {

เมื่อสังเกตบล็อก help ที่ด้านล่าง คุณต้องใช้การปิด move เนื่องจากเมื่อฟังก์ชัน new สิ้นสุด ตัวแปร button บนสแต็กจะอยู่นอกขอบเขต วิธีเดียวที่จะหลีกเลี่ยงสิ่งนี้ได้คือย้ายความเป็นเจ้าของไปยังตัวโทรกลับเอง ดังนั้นคุณจะเปลี่ยน

let tmp_callback = Box::new(|| {

to

let tmp_callback = Box::new(move || {

ตอนนี้ คุณจะได้รับข้อผิดพลาดที่สอง:

    error[E0382]: use of moved value: `button`
   --> src/main.rs:107:9
    |
102 |         let tmp_callback = Box::new(move || {
    |                                     ------- value moved (into closure) here
...
107 |         button
    |         ^^^^^^ value used here after move
    |
    = note: move occurs because `button` has type `std::sync::Arc<Button>`, which does not implement the `Copy` trait

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

หวังว่าวิธีแก้ปัญหาจะเป็นอย่างที่คุณเดาได้ คุณต้องทำสำเนาที่คุณสามารถเป็นเจ้าของได้ คุณจะต้องการเปลี่ยนแปลงแล้ว

let tmp_callback = Box::new(move || {
    button.do_stuff();

to

let button_clone = button.clone();
let tmp_callback = Box::new(move || {
    button_clone.do_stuff();

ตอนนี้คุณได้สร้างวัตถุ Button ใหม่และส่งคืน Arc สำหรับวัตถุนั้นเอง ในขณะเดียวกันก็ให้สิทธิ์ความเป็นเจ้าของ Arc ที่สองแก่ตัวโทรกลับด้วย

อัปเดต

จากความคิดเห็นของคุณ มีปัญหาเรื่องการขึ้นต่อกันแบบวนที่นี่จริงๆ เนื่องจากวัตถุ Clickable ของคุณเป็นเจ้าของการอ้างอิงถึง Button ในขณะที่ Button เป็นเจ้าของการอ้างอิงถึง Clickable วิธีที่ง่ายที่สุดในการแก้ไขปัญหานี้คือการอัปเดตโค้ดนั้นเป็นครั้งที่สาม

let button_clone = button.clone();
let tmp_callback = Box::new(move || {
    button_clone.do_stuff();

to

let button_weak = Arc::downgrade(&button);
let tmp_callback = Box::new(move || {
    if let Some(button) = button_weak.upgrade() {
        button.do_stuff();
    }
});

ดังนั้น Clickable จะเก็บเฉพาะการอ้างอิงที่อ่อนแอไปยัง Button และหากไม่มีการอ้างอิง Button อีกต่อไป การโทรกลับจะไม่ดำเนินการ

คุณอาจต้องการพิจารณาสร้าง clickables รายการของการอ้างอิง Weak แทนการอ้างอิงที่รัดกุม เพื่อให้คุณสามารถลบรายการออกจากรายการได้เมื่อรายการที่อ้างอิงถูกลบออก

person loganfsmyth    schedule 17.08.2017
comment
ใช่แล้ว นั่นเป็นวิธีแก้ปัญหาที่ฉันคิดขึ้นมาด้วย มีวิธีอื่นที่ทำให้แน่ใจได้ว่าปุ่มภายในที่คลิกได้นั้นได้รับการทำความสะอาดเมื่อปุ่มหลุดไปตลอดอายุการใช้งานหรือไม่ ยังไงก็ตาม ขอบคุณสำหรับความพยายามของคุณ! - person hodasemi; 18.08.2017
comment
อัปเดตคำตอบแล้ว - person loganfsmyth; 18.08.2017
comment
ขอบคุณมาก. นอกจากนี้การโทรกลับของฉันตอนนี้กลายเป็นสัตว์ประหลาดแล้วเพราะฉันมีอาร์คมากถึง 4 อาร์คโดยที่ฉันต้องทำแบบเดียวกับปุ่ม - person hodasemi; 18.08.2017
comment
คุณยังสามารถพิจารณาสร้าง Clickable ลักษณะ จากนั้นนำคุณลักษณะนั้นไปใช้กับ Button และอื่นๆ จากนั้นคุณจะลงทะเบียนสิ่งนั้นแทน - person loganfsmyth; 18.08.2017