Как передать ссылку на замыкание в Rust?

Я использую wry, чтобы создать несколько веб-просмотров. Каждое представление имеет on_page_load_handler. Внутри обработчика мне нужно получить доступ к веб-представлению, например. перейдите на другой сайт. К сожалению, мне не удалось передать веб-представление в on_page_load_handler. Кажется, я понимаю проблему, но не могу найти решения. Это скорее проблема Rust, чем криво в частности.

Итак, как я могу получить доступ к веб-представлению внутри обработчика?

Это код, который я пытаюсь запустить:

use tao::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder
};
use wry::{
    PageLoadEvent,
    WebView,
    WebViewBuilder
};

fn main() {
    let event_loop = EventLoop::new();
    let window = WindowBuilder::new()
        .build(&event_loop)
        .expect("Failed to create window.");

    let mut views: Vec<WebView> = Vec::new();
    for i in 0..3 {
        views.push(WebViewBuilder::new_as_child(&window)
        .with_url("https://stackoverflow.com/")
        .with_on_page_load_handler(move |event, target_url| {
            views[i].load_url("https://com/"); // <-- Problem here
        }).build()
        .expect("Failed to create web view"));
    }

    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::Wait;
    
        match event {
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => *control_flow = ControlFlow::Exit,
            _ => ()
        }
    });
}

Ошибка ржавчины:

error[E0382]: borrow of moved value: `views`
  --> src/main.rs:19:3
   |
17 |     let mut views: Vec<WebView> = Vec::new();
   |         --------- move occurs because `views` has type `Vec<WebView>`, which does not implement the `Copy` trait
18 |     for i in 0..3 {
   |     ------------- inside of this loop
19 |         views.push(WebViewBuilder::new_as_child(&window)
   |         ^^^^^ value borrowed here after move
20 |         .with_url("https://stackoverflow.com/")
21 |         .with_on_page_load_handler(move |event, target_url| {
   |                                    ------------------------ value moved into closure here, in previous iteration of loop

error[E0505]: cannot move out of `views` because it is borrowed
  --> src/main.rs:21:30
   |
17 |     let mut views: Vec<WebView> = Vec::new();
   |         --------- binding `views` declared here
18 |     for i in 0..3 {
19 |         views.push(WebViewBuilder::new_as_child(&window)
   |         ----- ---- borrow later used by call
   |         |
   |         borrow of `views` occurs here
20 |         .with_url("https://stackoverflow.com/")
21 |         .with_on_page_load_handler(move |event, target_url| {
   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^ move out of `views` occurs here
22 |             views[i].load_url("https://com/");
   |             ----- move occurs due to use in closure

Подпись with_on_page_load_handler:

pub fn with_on_page_load_handler(
  mut self,
  handler: impl Fn(PageLoadEvent, String) + 'static,
) -> Self

Аргументы handler бесполезны для получения доступа к встроенному WebView, поскольку ни PageLoadEvent, ни String не предоставляют к нему доступ.
Заранее спасибо! (Меня не волнуют правила. Я ценю каждого из вас за то, что вы нашли время взглянуть на это.)

Разве это не приведет к многократной загрузке одной и той же страницы снова и снова?

drewtato 29.06.2024 22:09

@drewtato да, было бы. это всего лишь минимальный пример. проблема в том, что я не могу получить доступ к представлениям [i] внутри замыкания. Я полагаю, что это как-то связано с владением, но я понятия не имею, как это можно исправить «путем Rust».

user3563584 30.06.2024 01:29

Я думаю, это будет примерно так: play.rust-lang.org/… Было бы проще, если бы у вас был какой-то контроллер вне всего этого и вы передавали бы сообщения между ним и замыканием.

drewtato 30.06.2024 02:28

@drewtato это уже очень близко. обработчик теперь имеет доступ к представлению. к сожалению, работает только первое представление, два других, похоже, пропали. Они больше не появляются. play.rust-lang.org/…

user3563584 30.06.2024 16:11

ОХХХ, я понимаю, что пошло не так. Макрос vec! клонирует предмет, который вы ему даете, а это значит, что все эти Арки — одна и та же Арка. Попробуйте это: play.rust-lang.org/… Я также исправил проблему с помощью expect.

drewtato 30.06.2024 20:15

@drewtato ты действительно сделал это! Большое спасибо. Я узнал здесь несколько вещей. Если вы опубликуете это как ответ, я отмечу это как решенное.

user3563584 30.06.2024 20:42
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
6
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть две проблемы, которые вам нужно решить. Во-первых, значения в Vec являются самореферентными. Я решил эту проблему, сохранив значения в Arc, а затем переместив ссылку Weak в замыкание. Во-вторых, замыкание должно ссылаться на что-то, чего не существует до тех пор, пока замыкание не будет создано. Это решается с помощью OnceLock, что позволяет настраивать его содержимое после создания и без необходимости использовать эксклюзивную ссылку (&mut).

use std::sync::{Arc, OnceLock};

// Using `vec![v; N]` clones `v`, so we can't use it here since each
// `Arc` needs to be separate.
let views: Vec<Arc<OnceLock<WebView>>> = (0..3).map(|_| Arc::new(OnceLock::new())).collect();
for view_arc in &views {
    let view_weak = Arc::downgrade(view_arc);
    let view: WebView = WebViewBuilder::new_as_child(&window)
        .with_url("https://stackoverflow.com/")
        .with_on_page_load_handler(move |event, target_url| {
            view_weak
                .upgrade()
                .expect("view has been dropped")
                .get()
                .expect("view was not initialized (this shouldn't happen)")
                .load_url("https://com/")
                .expect("failed loading url");
        })
        .build()
        .expect("Failed to create web view");

    // `expect` doesn't work when the error type doesn't impl `Debug`, so
    // instead this uses a plain `panic!`.
    view_arc.set(view).unwrap_or_else(|_| {
        panic!("something else initialized the cell (this shouldn't happen)")
    });
}

Я также только что заметил, что wry не обязательно должен быть Send, поэтому вместо этого вы можете использовать Vec<Rc<OnceCell<WebView>>>, но разница в производительности между ними должна быть чрезвычайно маленькой, а остальное должно быть в основном идентичным.

drewtato 30.06.2024 22:01

Другие вопросы по теме