Я использую 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 да, было бы. это всего лишь минимальный пример. проблема в том, что я не могу получить доступ к представлениям [i] внутри замыкания. Я полагаю, что это как-то связано с владением, но я понятия не имею, как это можно исправить «путем Rust».
Я думаю, это будет примерно так: play.rust-lang.org/… Было бы проще, если бы у вас был какой-то контроллер вне всего этого и вы передавали бы сообщения между ним и замыканием.
@drewtato это уже очень близко. обработчик теперь имеет доступ к представлению. к сожалению, работает только первое представление, два других, похоже, пропали. Они больше не появляются. play.rust-lang.org/…
ОХХХ, я понимаю, что пошло не так. Макрос vec! клонирует предмет, который вы ему даете, а это значит, что все эти Арки — одна и та же Арка. Попробуйте это: play.rust-lang.org/… Я также исправил проблему с помощью expect.
@drewtato ты действительно сделал это! Большое спасибо. Я узнал здесь несколько вещей. Если вы опубликуете это как ответ, я отмечу это как решенное.

Есть две проблемы, которые вам нужно решить. Во-первых, значения в 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>>>, но разница в производительности между ними должна быть чрезвычайно маленькой, а остальное должно быть в основном идентичным.
Разве это не приведет к многократной загрузке одной и той же страницы снова и снова?