Skip to content

Instantly share code, notes, and snippets.

@bohdaq
Last active March 18, 2023 12:28
Show Gist options
  • Save bohdaq/4881924b363ae69dda7eef1204d15a27 to your computer and use it in GitHub Desktop.
Save bohdaq/4881924b363ae69dda7eef1204d15a27 to your computer and use it in GitHub Desktop.
Using generics in Rust, implementation example and explanation
// real world example from json module of rust-web-server: https://github.com/bohdaq/rust-web-server/tree/main/src/json
// defining generics, library code
pub struct JSONArrayOfObjects<T> {
_item: T, // added to eliminate compiler error
}
pub trait New {
fn new() -> Self;
}
impl<T: New> JSONArrayOfObjects<T> {
pub fn new() -> T {
T::new()
}
}
impl<T: ToJSON> JSONArrayOfObjects<T> {
pub fn to_json(items : Vec<T>) -> Result<String, String> {
let mut json_vec = vec![];
json_vec.push(SYMBOL.opening_square_bracket.to_string());
for (pos, item) in items.iter().enumerate() {
json_vec.push(item.to_json_string());
if pos != items.len() - 1 {
json_vec.push(SYMBOL.comma.to_string());
json_vec.push(SYMBOL.new_line_carriage_return.to_string());
}
}
json_vec.push(SYMBOL.closing_square_bracket.to_string());
let result = json_vec.join(SYMBOL.empty_string);
Ok(result)
}
}
impl<T: FromJSON + New> JSONArrayOfObjects<T> {
pub fn from_json(json : String) -> Result<Vec<T>, String> {
let items = RawUnprocessedJSONArray::split_into_vector_of_strings(json).unwrap();
let mut list: Vec<T> = vec![];
for item in items {
let mut object = T::new();
object.parse(item).unwrap();
list.push(object);
}
Ok(list)
}
}
pub trait ToJSON {
fn list_properties() -> Vec<JSONProperty>;
fn get_property(&self, property_name: String) -> JSONValue;
fn to_json_string(&self) -> String;
}
pub trait FromJSON {
fn parse_json_to_properties(&self, json_string: String) -> Result<Vec<(JSONProperty, JSONValue)>, String>;
fn set_properties(&mut self, properties: Vec<(JSONProperty, JSONValue)>) -> Result<(), String>;
fn parse(&mut self, json_string: String) -> Result<(), String>;
}
// implementing generics, library user code
pub struct ExampleObject {
pub prop_a: String,
pub prop_b: bool,
pub prop_c: bool,
pub prop_d: i128,
pub prop_e: f64
}
impl New for ExampleObject {
fn new() -> Self {
ExampleObject {
prop_a: "".to_string(),
prop_b: false,
prop_c: false,
prop_d: 0,
prop_e: 0.0,
}
}
}
impl FromJSON for ExampleObject {
fn parse_json_to_properties(&self, json_string: String) -> Result<Vec<(JSONProperty, JSONValue)>, String> {
let boxed_parse = JSON::parse_as_properties(json_string);
if boxed_parse.is_err() {
let message = boxed_parse.err().unwrap();
return Err(message)
}
let properties = boxed_parse.unwrap();
Ok(properties)
}
fn set_properties(&mut self, properties: Vec<(JSONProperty, JSONValue)>) -> Result<(), String> {
for (property, value) in properties {
if property.property_name == "prop_a" {
if value.string.is_some() {
self.prop_a = value.string.unwrap();
}
}
if property.property_name == "prop_b" {
if value.bool.is_some() {
self.prop_b = value.bool.unwrap();
}
}
if property.property_name == "prop_c" {
if value.bool.is_some() {
self.prop_c = value.bool.unwrap();
}
}
if property.property_name == "prop_d" {
if value.i128.is_some() {
self.prop_d = value.i128.unwrap();
}
}
if property.property_name == "prop_e" {
if value.f64.is_some() {
self.prop_e = value.f64.unwrap();
}
}
}
Ok(())
}
fn parse(&mut self, json_string: String) -> Result<(), String> {
let boxed_properties = self.parse_json_to_properties(json_string);
if boxed_properties.is_err() {
let message = boxed_properties.err().unwrap();
return Err(message);
}
let properties = boxed_properties.unwrap();
let boxed_set = self.set_properties(properties);
if boxed_set.is_err() {
let message = boxed_set.err().unwrap();
return Err(message);
}
Ok(())
}
}
impl ToJSON for ExampleObject {
fn list_properties() -> Vec<JSONProperty> {
let mut list = vec![];
let property = JSONProperty { property_name: "prop_a".to_string(), property_type: JSON_TYPE.string.to_string() };
list.push(property);
let property = JSONProperty { property_name: "prop_b".to_string(), property_type: JSON_TYPE.boolean.to_string() };
list.push(property);
let property = JSONProperty { property_name: "prop_c".to_string(), property_type: JSON_TYPE.boolean.to_string() };
list.push(property);
let property = JSONProperty { property_name: "prop_d".to_string(), property_type: JSON_TYPE.integer.to_string() };
list.push(property);
let property = JSONProperty { property_name: "prop_e".to_string(), property_type: JSON_TYPE.number.to_string() };
list.push(property);
list
}
fn get_property(&self, property_name: String) -> JSONValue {
let mut value = JSONValue::new();
if property_name == "prop_a".to_string() {
let string : String = self.prop_a.to_owned();
value.string = Some(string);
}
if property_name == "prop_b".to_string() {
let boolean : bool = self.prop_b;
value.bool = Some(boolean);
}
if property_name == "prop_c".to_string() {
let boolean : bool = self.prop_c;
value.bool = Some(boolean);
}
if property_name == "prop_d".to_string() {
let integer : i128 = self.prop_d;
value.i128 = Some(integer);
}
if property_name == "prop_e".to_string() {
let floating_point_number: f64 = self.prop_e;
value.f64 = Some(floating_point_number);
}
value
}
fn to_json_string(&self) -> String {
let mut processed_data = vec![];
let properties = ExampleObject::list_properties();
for property in properties {
let value = self.get_property(property.property_name.to_string());
processed_data.push((property, value));
}
JSON::to_json_string(processed_data)
}
}
// using generics, library user code, example 1
let obj = ExampleObject::new();
let obj2 = ExampleObject {
prop_a: "test".to_string(),
prop_b: true,
prop_c: false,
prop_d: 10,
prop_e: 2.2,
};
let actual = JSONArrayOfObjects::<ExampleObject>::to_json(vec![obj, obj2]).unwrap();
// using generics, library user code, example 2
let json = "[{\r\n \"prop_a\": \"some text\",\r\n \"prop_b\": false}]".to_string();
let parsed_list : Vec<ExampleObject> = JSONArrayOfObjects::<ExampleObject>::from_json(json).unwrap();
@bohdaq
Copy link
Author

bohdaq commented Mar 17, 2023

Use cases for generics:

  • struct needs to have a field of the specific type (line 4)
  • function needs to receive a parameter of generic type (line 18)
  • function needs to return a specific generic type (line 12)
  • there is a need to invoke a specific function on the generic type (line 40)

@bohdaq
Copy link
Author

bohdaq commented Mar 17, 2023

As for me, the definition of the generic is not clear (lines 5, 13, 19 and 37).
impl<T: New> JSONArrayOfObjects<T> is not intuitive to understand

@bohdaq
Copy link
Author

bohdaq commented Mar 17, 2023

impl<T: New> JSONArrayOfObjects<T> basically means that somewhere within JSONArrayOfObjects fileds or functions we are going to use specific type T which implements New trait

@bohdaq
Copy link
Author

bohdaq commented Mar 17, 2023

JSONArrayOfObjects::<ExampleObject>::to_json(vec![obj, obj2]) here JSONArrayOfObjects function to_json accepts as a parameter vector of any type that implements ToJSON trait (line 222)

JSONArrayOfObjects::<ExampleObject>::from_json(json) here JSONArrayOfObjects function from_json returns result containing vector of any type that implements FromJSON and New traits (line 227)

Prefix JSONArrayOfObjects::<ExampleObject> is where compiler being told explicitly to use ExampleObject as generic type

So, on one hand, there is a compiler that needs to guarantee type safety at compile time and on another hand there is a developer that wants to write extensible code.

JSONArrayOfObjects is designed to be used as a library, so it can accept a large variety of unknown types.

Generics is a way to allow usage of any type which fulfills specific requirements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment