如何在GObject中存储未实现Default trait的Rust对象
实现方案
直接用glib内置的Boxed类型机制即可,不需要用裸指针、也不需要强制给业务结构体实现Default,完全满足「单属性暴露、只读、仅Rust侧创建实例」的需求:
第一步:给业务结构体派生Boxed trait
GObject系统支持通过Boxed类型包装任意Rust自定义类型,只需要给结构体实现Clone trait、派生glib::Boxed即可,不需要修改原有业务逻辑:
// 注意Encoding枚举也需要做同样处理:简单枚举用#[derive(glib::Enum)],复杂结构用Boxed #[derive(Clone, glib::Boxed)] #[boxed_type(name = "Chapter")] pub struct Chapter { pub number: u32, pub filename: String, pub thumbnail_filename: Option<String>, pub preview_video_filename: Option<String>, } #[derive(Clone, glib::Boxed)] #[boxed_type(name = "Video")] pub struct Video { pub number: u32, pub encoding: Encoding, pub chapters: Vec<Chapter>, }
如果觉得结构体Clone成本高,可以把内部字符串、Vec等字段换成Arc<T>包装,Clone时仅拷贝指针,开销可以忽略。
第二步:正确实现GObject包装类
不要给内部实现块自动派生Default,手动实现Default将存储字段初始化为空,同时注册单个构造期只读的video属性即可:
mod imp { use std::cell::RefCell; use glib::prelude::*; use glib::subclass::prelude::*; use super::Video; pub struct VideoObject { data: RefCell<Option<Video>>, } // 手动实现Default,不需要Video实现Default impl Default for VideoObject { fn default() -> Self { Self { data: RefCell::new(None) } } } #[glib::object_subclass] impl ObjectSubclass for VideoObject { const NAME: &'static str = "VideoObject"; type Type = super::VideoObject; } impl ObjectImpl for VideoObject { fn properties() -> &'static [glib::ParamSpec] { use std::sync::OnceLock; static PROPS: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new(); PROPS.get_or_init(|| { vec![ // 注册为构造期设置、只读的Boxed属性,创建后不可修改 glib::ParamSpecBoxed::builder::<Video>("video") .construct_only() .build() ] }) } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { if pspec.name() == "video" { let video = value.get::<Video>().expect("属性类型不匹配"); // 加判断防止重复设置,符合只读需求 if self.data.borrow().is_some() { panic!("video属性为只读,不可重复设置"); } self.data.replace(Some(video)); return; } unimplemented!() } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { if pspec.name() == "video" { return self.data.borrow().as_ref().expect("video未初始化").to_value(); } unimplemented!() } } } glib::wrapper! { pub struct VideoObject(ObjectSubclass<imp::VideoObject>); } impl VideoObject { pub fn new(video: Video) -> Self { Object::builder() .property("video", &video) .build() } // Rust侧直接取值,不需要走GObject属性逻辑,效率更高 pub fn get_video(&self) -> Video { self.imp().data.borrow().as_ref().unwrap().clone() } }
方案说明
- 不需要逐个暴露结构体字段为GObject属性,整个Video结构体作为单个属性存在
- 不需要给业务结构体实现Default,避免无意义的默认实例构造
- 全程类型安全,内存由GObject类型系统自动管理,不会出现裸指针的内存泄漏、野指针问题
- 实例只能由Rust侧创建完成后传入
VideoObject::new,GTK侧无法自行构造Video实例,符合业务分层要求 - 如果你不需要在.ui文件的GTK表达式中绑定video属性,甚至可以完全省略属性注册的
properties/set_property/property三段逻辑,直接在new方法里把video存入data字段即可,代码更简洁,完全满足ListView的Rust侧绑定需求。
内容的提问来源于stack exchange,提问作者Hellstorm
相关产品推荐
相关产品推荐

