I am basically following the gtk-rs book to create a simple app. I have the following rust struct, which I want to display in a ListView. This struct is basically read-only and should not be edited, so I do not want to expose it's members as properties and I also do not want to create it from GTK itself (it's created inside the rust-only business logic). I would like to expose the entire struct as one property.
pub struct Video {
pub number: u32,
pub encoding: Encoding,
pub chapters: Vec<Chapter>,
}
pub struct Chapter {
pub number: u32,
pub filename: String,
pub thumbnail_filename: Option<String>,
pub preview_video_filename: Option<String>,
}
How can I wrap this inside a GObject? I'm trying to create the following wrapper:
mod imp {
#[derive(Default)]
pub struct VideoObject {
pub data: Rc<RefCell<Video>>,
}
#[glib::object_subclass]
impl ObjectSubclass for VideoObject {
const NAME: &'static str = "VideoObject";
type Type = super::VideoObject;
}
impl ObjectImpl for VideoObject {}
}
pub const VIDEO_PROPERTY: &str = "video";
glib::wrapper! {
pub struct VideoObject(ObjectSubclass<imp::VideoObject>);
}
impl VideoObject {
pub fn new(video: Video) -> Self {
Object::new(&[]).expect("Could not create VideoObject")
}
}
This fails because Video does not implement Default. I thought about wrapping it inside an Option like data: Rc<RefCell<Option<Video>>>, which compiles. Unfortunately, then I'm stuck on how to set it as a property.
I guess one way would be to use a ParamSpecPointer, box and leak Video and then pass it before, but this strucks me as unsafe and ugly...
Is there a better way to do it?
You can directly access the "imp" object and set fields manually. No need to transform everything into GObject properties:
impl VideoObject {
pub fn new(video: Video) -> Self {
let object = Object::new(&[]).unwrap();
let imp = imp::VideoObject::from_instance(&object);
imp.data.replace(Some(video));
object
}
}
Related
In a library I have a compressor that supports two "modes" with different APIs, but the same data model some common logic. Here's how I've written it DRY:
struct BaseCompressor<M: Mode> {...}
impl<M: Mode> BaseCompressor<M> {
/// some docs...
pub fn header(...) -> ... {...}
}
pub type CompressorA = BaseCompressor<ModeA>;
impl CompressorA {
/// some docs...
pub fn chunk(...) -> ... {...}
}
pub type CompressorB = BaseCompressor<ModeB>
impl CompressorB {
/// some docs...
pub fn data_page(...) -> ... {...}
}
I'm quite happy with structuring the logic this way so that CompressorA and CompressorB share data members and logic for header. However, it screws up the generated docs, rendering absolutely nothing meaningful for CompressorA and CompressorB. I would rather not expose BaseCompressor in the public API. Is there any way I can get full docs for CompressorA and CompressorB? Or perhaps a better way I can structure this?
Trying your example locally (slightly adjusted), I get this result from cargo doc --open:
Would this satisfy your needs? I do have another way to structure your code which I would prefer:
Since you said the APIs for the modes are different, you could just have 2 separate structs that both contain a BaseCompressor:
// Private base type
struct BaseCompressor;
impl BaseCompressor {
// Some shared logic
pub fn header() {}
}
// Mode A
pub struct CompressorA {
inner: BaseCompressor
}
impl CompressorA {
// API for mode A
pub fn chunk() {}
}
// Mode B
pub struct CompressorB {
inner: BaseCompressor
}
impl CompressorB {
// API for mode B
pub fn data_page() {}
}
I need a Store struct like this:
pub struct Store {
pub players: HashMap<(String, i64), Player>,
pub teams: HashMap<(String, i64), Team>,
}
impl Store {
pub fn new() -> Self {
Self {
players: HashMap::new(),
team: HashMap::new(),
}
}
}
The values in those HashMap are used as "shared, request-scoped memory" and then deleted (using .remove(k)).
QUESTIONs
Since I don't care if two or more requests write/read the same entries at the same time (very unlikely) should I still use Mutex?
// Like this?
let store = Arc::new(Mutex::new(Store::new()))
// Or this?
pub fn new() -> Self {
Self {
players: Mutex::new(HashMap::new()),
team: Mutex::new(HashMap::new()),
}
}
Since the store is created when the application starts and is never destroyed, would it be useful to create a 'static one thus avoiding the use of Arc too?
// Something like:
const store: &mut Store = &mut Store::new();
UPDATE:
I'm trying to use this store to avoid nested structs issues like:
struct Player {
id: String,
team: Box<Team>, // see the Box here
}
So I'm trying to use flat, plain data like:
struct Player {
id: String,
team_id: String,
}
but I need something to use as store.
I need to serialize a struct from a remote crate and all of the fields in the struct are private. There are getter's implemented in the remote struct to get those values. I am following this guidance and got it to work just fine for primitive types. However, I'm struggling with how to implement this for non-primitive types (ie: String) that the remote struct contains.
Below is a small piece of what I've implemented to frame the issue. The DataWrapper struct simply wraps Data, where Data is the remote struct.
#[derive(Serialize)]
pub struct DataWrapper {
#[serde(with = "DataDef")]
pub data: Data,
}
#[derive(Serialize)]
#[serde(remote = "remote_crate::data::Data")]
pub struct DataDef {
#[serde(getter = "Data::image_id")] // This works
image_id: u16,
#[serde(getter = "Data::description")] // This does not work
description: String,
}
The error I get when compiling this is
#[derive(Serialize)]
^^^^^^^^^ expected struct `std::string::String`, found `&str`
This makes sense, since the getter Data::description returns &str rather than a String. But, I'm not seeing a way in my code to coerce this so the compiler is happy.
If I change DataDef::description to be &str instead of String, then I have to implement lifetimes. But, when I do that, the compiler then says the remote "struct takes 0 lifetime arguments".
Appreciate any tips on how I can serialize this and other non-primitive types.
One approach you could do, so that you have full control of the serialization. Is to have the data wrapper be a copy of the struct fields you need, instead of the entire remote struct. Then you can implement From<remote_crate::data::Data> for DataWrapper and use serde without trying to coerce types.
#[derive(Serialize)]
pub struct Data {
image_id: u16,
description: String,
}
impl From<remote_crate::data::Data> for Data {
fn from(val: remote_crate::data::Data) -> Self {
Self {
image_id: val.image_id,
description: val.description.to_string(),
}
}
}
// Then you could use it like this:
// let my_data: Data = data.into();
I couldn't get it to work with an &str, but if you're OK with an allocation, you can write a custom getter:
mod local {
use super::remote::RemoteData;
use serde::{Deserialize, Serialize};
fn get_owned_description(rd: &RemoteData) -> String {
rd.description().into()
}
#[derive(Serialize)]
#[serde(remote = "RemoteData")]
pub struct DataDef {
#[serde(getter = "get_owned_description")]
description: String,
}
}
mod remote {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct RemoteData {
description: String,
}
impl RemoteData {
pub fn description(&self) -> &str {
&self.description
}
}
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e9c7c0b069d7e16b6faac2fa2b840c72
Example struct that will be created and serialized often:
pub struct PriceMessage
{
pub message_type: String, // this will always be "price"
}
I want "message_type" to always equal e.g. "price" in order to save allocating a new string every time I create and serialize this struct using serde_json crate.
If you wanted to have a compile constant default string value for a field, that could at run time later be replaced by a heap allocated string, you could do something like this:
pub struct SomeStruct {
pub some_field: Cow<'static, str>,
}
impl SomeStruct {
const SOME_FIELD_DEFAULT: Cow<'static, str> = Cow::Borrowed("Foobar!");
pub fn new() -> Self {
Self {
some_field: Self::SOME_FIELD_DEFAULT,
}
}
}
However if you want to have an actually constant field, this doesn't really make much sense in rust, and you should consider just using an associated constant.
I need to serialize some data into files. For the sake of memory efficiency, I want to use the default compact serializer of MessagePack (MsgPack), as it only serializes field values w/o their names. I also want to be able to make changes to the data structure in future versions, which obviously can't be done w/o also storing some meta/versioning information. I imagine the most efficient way to do it is to simply use some "header" field for that purpose. Here is an example:
pub struct Data {
pub version: u8,
pub items: Vec<Item>,
}
pub struct Item {
pub field_a: i32,
pub field_b: String,
pub field_c: i16, // Added in version 3
}
Can I do something like that in rmp-serde (or maybe some other crate?) - to somehow annotate that a certain struct field should only be taken into account for specific file versions?
You can achieve this by writing a custom deserializer like this:
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Serialize)]
pub struct Data {
pub version: u8,
pub items: Vec<Item>,
}
#[derive(Serialize)]
pub struct Item {
pub field_a: i32,
pub field_b: String,
pub field_c: i16, // Added in version 3
}
impl<'de> Deserialize<'de> for Data {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Inner structs, used for deserializing only
#[derive(Deserialize)]
pub struct InnerData {
version: u8,
items: Vec<InnerItem>,
}
#[derive(Deserialize)]
pub struct InnerItem {
field_a: i32,
field_b: String,
field_c: Option<i16>, // Added in version 3 - note that this field is optional
}
// Deserializer the inner structs
let inner_data = InnerData::deserialize(deserializer)?;
// Get the version so we can add custom logic based on the version later on
let version = inner_data.version;
// Map the InnerData/InnerItem structs to Data/Item using our version based logic
Ok(Data {
version,
items: inner_data
.items
.into_iter()
.map(|item| {
Ok(Item {
field_a: item.field_a,
field_b: item.field_b,
field_c: if version < 3 {
42 // Default value
} else {
// Get the value of field_c
// If it's missing return an error, since it's required since version 3
// Otherwise return the value
item.field_c
.map_or(Err(D::Error::missing_field("field_c")), Ok)?
},
})
})
.collect::<Result<_, _>>()?,
})
}
}
Short explanation how the deserializer works:
We create a "dumb" inner struct which is a copy of your structs but the "new" fields are optional
We deserialize to the new inner structs
We map from our inner to our outer structs using version-based logic
If one of the new fields is missing in a new version we return a D::Error::missing_field error