Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Rust): Support serialization and deserialization of trait objects #1797

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust/fury-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,7 @@ pub enum Error {

#[error("Unregistered type when serializing or deserializing object of Any type: {value:?}")]
UnregisteredType { value: u32 },

#[error("Trait object upcast failed")]
ConvertToTraitObjectError {},
}
34 changes: 28 additions & 6 deletions rust/fury-core/src/fury.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
// specific language governing permissions and limitations
// under the License.

use chrono::{NaiveDate, NaiveDateTime};

use crate::buffer::{Reader, Writer};
use crate::error::Error;
use crate::resolver::class_resolver::{ClassInfo, ClassResolver};
use crate::resolver::context::ReadContext;
use crate::resolver::context::WriteContext;
use crate::serializer::{Serializer, StructSerializer};
use crate::serializer::Serializer;
use crate::types::{config_flags, Language, Mode, SIZE_OF_REF_AND_TYPE};

pub struct Fury {
Expand All @@ -30,14 +32,34 @@ pub struct Fury {

impl Default for Fury {
fn default() -> Self {
Fury {
let mut fury = Fury {
mode: Mode::SchemaConsistent,
class_resolver: ClassResolver::default(),
}
};
fury.register_internal_type::<u8>();
fury.register_internal_type::<i8>();
fury.register_internal_type::<u16>();
fury.register_internal_type::<i16>();
fury.register_internal_type::<u32>();
fury.register_internal_type::<i32>();
fury.register_internal_type::<u64>();
fury.register_internal_type::<i64>();
fury.register_internal_type::<f32>();
fury.register_internal_type::<f64>();
fury.register_internal_type::<String>();
fury.register_internal_type::<bool>();
fury.register_internal_type::<NaiveDate>();
fury.register_internal_type::<NaiveDateTime>();
// todo generic type
fury
}
}

impl Fury {
fn register_internal_type<T: Serializer>(&mut self) {
self.register::<i32>(T::get_type_id(self) as u32);
}

pub fn mode(mut self, mode: Mode) -> Self {
self.mode = mode;
self
Expand Down Expand Up @@ -90,8 +112,8 @@ impl Fury {
&self.class_resolver
}

pub fn register<T: 'static + StructSerializer>(&mut self, id: u32) {
let class_info = ClassInfo::new::<T>(self, id);
self.class_resolver.register::<T>(class_info, id);
pub fn register<T: 'static + Serializer>(&mut self, id: u32) -> &mut ClassInfo {
self.class_resolver
.register::<T>(ClassInfo::new::<T>(self, id), id)
}
}
1 change: 1 addition & 0 deletions rust/fury-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod buffer;
pub mod error;
pub mod fury;
pub mod meta;
pub mod raw;
pub mod resolver;
pub mod row;
pub mod serializer;
Expand Down
42 changes: 42 additions & 0 deletions rust/fury-core/src/raw/maybe_trait_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::error::Error;
use mem::transmute;
use std::any::TypeId;
use std::mem;

pub struct MaybeTraitObject {
ptr: *const u8,
type_id: TypeId,
}

impl MaybeTraitObject {
pub fn new<T: 'static>(value: T) -> MaybeTraitObject {
let ptr = unsafe { transmute::<Box<T>, *const u8>(Box::new(value)) };
let type_id = TypeId::of::<T>();
MaybeTraitObject { ptr, type_id }
}

pub fn to_trait_object<T: 'static>(self) -> Result<T, Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user don't call this function, then T is leaked?

If it's, we can impl Drop for MaybeTraitObject to release memory..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch, I will fixed it.

if self.type_id == TypeId::of::<T>() {
Ok(unsafe { *(transmute::<*const u8, Box<T>>(self.ptr)) })
} else {
Err(Error::ConvertToTraitObjectError {})
}
}
}
18 changes: 18 additions & 0 deletions rust/fury-core/src/raw/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

pub mod maybe_trait_object;
108 changes: 46 additions & 62 deletions rust/fury-core/src/resolver/class_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,99 +18,83 @@
use super::context::{ReadContext, WriteContext};
use crate::error::Error;
use crate::fury::Fury;
use crate::serializer::StructSerializer;
use crate::raw::maybe_trait_object::MaybeTraitObject;
use crate::serializer::{Serializer, SS};
use std::any::TypeId;
use std::{any::Any, collections::HashMap};

pub struct Harness {
serializer: fn(&dyn Any, &mut WriteContext),
deserializer: fn(&mut ReadContext) -> Result<Box<dyn Any>, Error>,
}

impl Harness {
pub fn new(
serializer: fn(&dyn Any, &mut WriteContext),
deserializer: fn(&mut ReadContext) -> Result<Box<dyn Any>, Error>,
) -> Harness {
Harness {
serializer,
deserializer,
}
}

pub fn get_serializer(&self) -> fn(&dyn Any, &mut WriteContext) {
self.serializer
}

pub fn get_deserializer(&self) -> fn(&mut ReadContext) -> Result<Box<dyn Any>, Error> {
self.deserializer
}
}
pub type TraitObjectDeserializer = fn(&mut ReadContext) -> Result<MaybeTraitObject, Error>;

pub struct ClassInfo {
type_def: Vec<u8>,
type_id: u32,
fury_type_id: u32,
rust_type_id: TypeId,
trait_object_serializer: fn(&dyn Any, &mut WriteContext),
trait_object_deserializer: HashMap<TypeId, TraitObjectDeserializer>,
}

fn serialize<T: 'static + Serializer>(this: &dyn Any, context: &mut WriteContext) {
let this = this.downcast_ref::<T>().unwrap();
T::serialize(this, context)
}

impl ClassInfo {
pub fn new<T: StructSerializer>(fury: &Fury, type_id: u32) -> ClassInfo {
pub fn new<T: Serializer>(fury: &Fury, type_id: u32) -> ClassInfo {
ClassInfo {
type_def: T::type_def(fury),
type_id,
fury_type_id: type_id,
rust_type_id: TypeId::of::<T>(),
trait_object_serializer: serialize::<T>,
trait_object_deserializer: T::get_trait_object_deserializer(),
}
}

pub fn get_type_id(&self) -> u32 {
self.type_id
pub fn associate(&mut self, type_id: TypeId, func: TraitObjectDeserializer) {
self.trait_object_deserializer.insert(type_id, func);
}

pub fn get_rust_type_id(&self) -> TypeId {
self.rust_type_id
}

pub fn get_fury_type_id(&self) -> u32 {
self.fury_type_id
}

pub fn get_type_def(&self) -> &Vec<u8> {
&self.type_def
}

pub fn get_serializer(&self) -> fn(&dyn Any, &mut WriteContext) {
self.trait_object_serializer
}

pub fn get_trait_object_deserializer<T: 'static>(&self) -> Option<&TraitObjectDeserializer> {
let type_id = TypeId::of::<T>();
self.trait_object_deserializer.get(&type_id)
}
}

#[derive(Default)]
pub struct ClassResolver {
serialize_map: HashMap<u32, Harness>,
type_id_map: HashMap<TypeId, u32>,
fury_type_id_map: HashMap<u32, TypeId>,
class_info_map: HashMap<TypeId, ClassInfo>,
}

impl ClassResolver {
pub fn get_class_info(&self, type_id: TypeId) -> &ClassInfo {
pub fn get_class_info_by_rust_type(&self, type_id: TypeId) -> &ClassInfo {
self.class_info_map.get(&type_id).unwrap()
}

pub fn register<T: StructSerializer>(&mut self, class_info: ClassInfo, id: u32) {
fn serializer<T2: 'static + StructSerializer>(this: &dyn Any, context: &mut WriteContext) {
let this = this.downcast_ref::<T2>();
match this {
Some(v) => {
T2::serialize(v, context);
}
None => todo!(),
}
}

fn deserializer<T2: 'static + StructSerializer>(
context: &mut ReadContext,
) -> Result<Box<dyn Any>, Error> {
match T2::deserialize(context) {
Ok(v) => Ok(Box::new(v)),
Err(e) => Err(e),
}
}
self.type_id_map.insert(TypeId::of::<T>(), id);
self.serialize_map
.insert(id, Harness::new(serializer::<T>, deserializer::<T>));
self.class_info_map.insert(TypeId::of::<T>(), class_info);
}

pub fn get_harness_by_type(&self, type_id: TypeId) -> Option<&Harness> {
self.get_harness(*self.type_id_map.get(&type_id).unwrap())
pub fn get_class_info_by_fury_type(&self, type_id: u32) -> &ClassInfo {
let type_id = self.fury_type_id_map.get(&type_id).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many unwrap here, how about return a Result?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The codebase contain too many unwrap, I will fix it one by one.

self.class_info_map.get(type_id).unwrap()
}

pub fn get_harness(&self, id: u32) -> Option<&Harness> {
self.serialize_map.get(&id)
pub fn register<T: Serializer>(&mut self, class_info: ClassInfo, id: u32) -> &mut ClassInfo {
let type_id = TypeId::of::<T>();
self.fury_type_id_map.insert(id, type_id);
self.class_info_map.insert(type_id, class_info);
self.class_info_map.get_mut(&type_id).unwrap()
}
}
2 changes: 1 addition & 1 deletion rust/fury-core/src/resolver/meta_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl<'a> MetaWriterResolver<'a> {
let index = self.type_defs.len();
self.type_defs.push(
fury.get_class_resolver()
.get_class_info(type_id)
.get_class_info_by_rust_type(type_id)
.get_type_def(),
);
self.type_id_index_map.insert(type_id, index);
Expand Down
55 changes: 10 additions & 45 deletions rust/fury-core/src/serializer/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
// specific language governing permissions and limitations
// under the License.

use crate::error::Error;
use crate::fury::Fury;
use crate::resolver::context::{ReadContext, WriteContext};
use crate::serializer::Serializer;
use crate::types::{FieldType, Mode, RefFlag};
use std::any::Any;

use super::{polymorph, Serializer};
use crate::{
error::Error,
fury::Fury,
resolver::context::{ReadContext, WriteContext},
types::FieldType,
};

impl Serializer for Box<dyn Any> {
fn reserved_space() -> usize {
0
Expand All @@ -40,48 +43,10 @@ impl Serializer for Box<dyn Any> {
}

fn serialize(&self, context: &mut WriteContext) {
context
.get_fury()
.get_class_resolver()
.get_harness_by_type(self.as_ref().type_id())
.unwrap()
.get_serializer()(self.as_ref(), context);
polymorph::serialize(self.as_ref(), self.as_ref().type_id(), context);
}

fn deserialize(context: &mut ReadContext) -> Result<Self, Error> {
let reset_cursor = context.reader.reset_cursor_to_here();
// ref flag
let ref_flag = context.reader.i8();

if ref_flag == (RefFlag::NotNullValue as i8) || ref_flag == (RefFlag::RefValue as i8) {
if context.get_fury().get_mode().eq(&Mode::Compatible) {
let meta_index = context.reader.i16();
let type_id = context.meta_resolver.get(meta_index as usize).get_type_id();
reset_cursor(&mut context.reader);
context
.get_fury()
.get_class_resolver()
.get_harness(type_id)
.unwrap()
.get_deserializer()(context)
} else {
let type_id = context.reader.i16();
reset_cursor(&mut context.reader);
context
.get_fury()
.get_class_resolver()
.get_harness(type_id as u32)
.unwrap()
.get_deserializer()(context)
}
} else if ref_flag == (RefFlag::Null as i8) {
Err(Error::Null)
} else if ref_flag == (RefFlag::Ref as i8) {
reset_cursor(&mut context.reader);
Err(Error::Ref)
} else {
reset_cursor(&mut context.reader);
Err(Error::BadRefFlag)
}
polymorph::deserialize::<Self>(context)
}
}
2 changes: 1 addition & 1 deletion rust/fury-core/src/serializer/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::serializer::Serializer;
use crate::types::{FieldType, FuryGeneralList, SIZE_OF_REF_AND_TYPE};
use std::mem;

impl<T> Serializer for Vec<T>
impl<T: 'static> Serializer for Vec<T>
where
T: Serializer + FuryGeneralList,
{
Expand Down
4 changes: 3 additions & 1 deletion rust/fury-core/src/serializer/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ use crate::types::{FieldType, FuryGeneralList, SIZE_OF_REF_AND_TYPE};
use std::collections::HashMap;
use std::mem;

impl<T1: Serializer + Eq + std::hash::Hash, T2: Serializer> Serializer for HashMap<T1, T2> {
impl<T1: Serializer + Eq + std::hash::Hash + 'static, T2: Serializer + 'static> Serializer
for HashMap<T1, T2>
{
fn write(&self, context: &mut WriteContext) {
// length
context.writer.var_int32(self.len() as i32);
Expand Down
Loading
Loading