Last active
March 18, 2023 12:28
-
-
Save bohdaq/4881924b363ae69dda7eef1204d15a27 to your computer and use it in GitHub Desktop.
Using generics in Rust, implementation example and explanation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 typeSo, 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.