【发布时间】:2015-11-05 03:19:03
【问题描述】:
我目前正在学习 Rust 并希望将其用于开发 GUI 基于 GTK+ 的应用程序。我的问题与注册回调有关 在这些回调中响应 GTK 事件/信号和变异状态。 我有一个可行但不优雅的解决方案,所以我想问一下是否有 是一种更简洁、更惯用的解决方案。
我已将我的代码实现为带有方法实现的结构,其中
该结构维护对 GTK 小部件的引用以及其他状态
它需要。它构造一个传递给
GtkWidget::connect*函数为了接收事件,绘制到一个
画布等。这可能会导致借用检查器出现问题,我现在将
解释。我有一些 工作 但(恕我直言)非理想代码,我会
显示。
初始的、无效的解决方案:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> RenderingAPITestWindow {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
width: width,
height: height,
};
instance.drawing_area.connect_draw(|widget, cairo_context| {
instance.on_draw(cairo_context);
instance.drawing_area.queue_draw();
Inhibit(true)
});
instance.drawing_area.connect_size_allocate(|widget, rect| {
instance.on_size_allocate(rect);
});
instance.window.show_all();
return instance;
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
}
上面的代码无法编译为闭包
RenderingAPITestWindow::new 创建并传递给调用
GtkWidget::connect* 方法尝试借用 instance。这
编译器声明闭包可能比其中的函数寿命更长
它们被声明并且instance 归外部函数所有,
因此问题。鉴于 GTK 可能会保留对这些闭包的引用
在不确定的时间内,我们需要一种方法
生命周期可以在运行时确定,因此我的下一个问题是
RenderingAPITestWindow 实例包含在其中
Rc<RefCell<...>>.
包装 RenderingAPITestWindow 实例编译但在运行时终止:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
width: width,
height: height,
};
let wrapped_instance = Rc::new(RefCell::new(instance));
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context| {
wrapped_instance_for_draw.borrow_mut().on_draw(cairo_context);
wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
});
let wrapped_instance_for_sizealloc = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect| {
wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);
});
wrapped_instance.borrow().window.show_all();
return wrapped_instance;
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
}
上述解决方案可以编译,但不是特别漂亮:
-
RenderingAPITestWindow::new返回一个Rc<RefCell<RenderingAPITestWindow>>而不是RenderingAPITestWindow - 访问
RenderingAPITestWindow的字段和方法很复杂 由于Rc<RefCell<...>>必须打开;现在需要wrapped_instance.borrow().some_method(...)而不仅仅是instance.some_method(...) - 每个闭包都需要它自己的
wrapped_instance的克隆;尝试 使用wrapped_instance会尝试借用一个对象—— 这次是包装器而不是RenderingAPITestWindow——也就是说RenderingAPITestWindow::new和以前一样拥有
虽然上述编译,但它在运行时死亡:
thread '<main>' panicked at 'RefCell<T> already borrowed', ../src/libcore/cell.rs:442
An unknown error occurred
这是由于对window.show_all() 的调用导致 GTK
初始化小部件层次结构,生成绘图区小部件
接收size-allocate 事件。访问窗口调用
show_all() 要求打开 Rc<RefCell<...>>(因此
wrapped_instance.borrow().window.show_all();) 和实例
借来的。在show_all() 返回时借用结束之前,GTK 调用
绘图区的size-allocate 事件处理程序,导致关闭
连接到它(上面的 4 行)被调用。关闭试图
借用对RenderingAPITestWindow 实例的可变引用
(wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);)
为了调用on_size_allocate 方法。这试图借用一个
可变引用,而第一个不可变引用仍在范围内。
第二次借用会导致运行时恐慌。
工作但是 - 恕我直言 - 我设法得到的不优雅的解决方案
到目前为止的工作是将RenderingAPITestWindow 拆分为两个结构,其中
由回调修改的可变状态移动到
单独的结构。
拆分 RenderingAPITestWindow 结构的可行但不优雅的解决方案:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::{Context, RectangleInt};
struct RenderingAPITestWindowState {
width: i32,
height: i32
}
impl RenderingAPITestWindowState {
fn new(width: i32, height: i32) -> RenderingAPITestWindowState {
return RenderingAPITestWindowState{width: width, height: height};
}
fn on_draw(&mut self, cairo_ctx: Context) {
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
}
fn on_size_allocate(&mut self, rect: &RectangleInt) {
self.width = rect.width as i32;
self.height = rect.height as i32;
}
}
struct RenderingAPITestWindow {
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: Rc<RefCell<RenderingAPITestWindowState>>
}
impl RenderingAPITestWindow {
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>> {
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let wrapped_state = Rc::new(RefCell::new(RenderingAPITestWindowState::new(width, height)))
;
let instance = RenderingAPITestWindow{window: window,
drawing_area: drawing_area,
state: wrapped_state.clone()
};
let wrapped_instance = Rc::new(RefCell::new(instance));
let wrapped_state_for_draw = wrapped_state.clone();
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context| {
wrapped_state_for_draw.borrow_mut().on_draw(cairo_context);
wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
});
let wrapped_state_for_sizealloc = wrapped_state.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect| {
wrapped_state_for_sizealloc.borrow_mut().on_size_allocate(rect);
});
wrapped_instance.borrow().window.show_all();
return wrapped_instance;
}
fn exit_on_close(&self) {
self.window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(true)
});
}
}
fn main() {
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: {}, Minor: {}", gtk::get_major_version(), gtk::get_minor_version());
let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
}
虽然上面的代码可以按要求工作,但我想找到一个更好的方法
为前进;我想问是否有人知道更好的方法
以上使编程过程相当复杂,需要
使用 Rc<RefCell<...>> 并拆分结构以满足 Rust 的借用规则。
【问题讨论】:
-
由于自动取消引用,我很确定
(*val.borrow())和(*val.borrow_mut())的所有实例都可以替换为val.borrow()和val.borrow_mut()。 -
“鉴于 GTK 可能会在未指定的时间内保留对这些闭包的引用” --- 对我来说,这是您需要使用的提示 @987654361 @如果多个闭包需要访问内存中的相同位置。
-
感谢您的提示;
(*val.borrow())的出现已被val.borrow()替换。这让事情变得更漂亮了! :)
标签: callback gtk rust idioms rust-gnome