Rust's core strength is its ability to provide memory safety and performance guarantees at compile-time, which is achieved through its ownership and borrowing system. But ability for the developer to handle a dynamic data types defined at run-time for the applications written in Rust is still important endeavor and it is improving versatility of the application while providing an ability to handle a wide range of use cases.
Various interpreted DSL languages created in Rust uses run-time dynamic data type system created for the specific language. Excellent crate Serde also implements trait Value that's allow to handle dynamic datatypes in run-time. But none of those solutions is not perfect for a Rust developers as they ether focus on specific DSL or handling JSON values.
rustdynamic is a crate, created for Rust language, implementing primitives that will be helping to Rust developer with the specific issue of handling dynamic data types defined at run-time. Currently, rustdynamic supports following data types:
Dynamic values are wrapped and stored inside a Value structure and could be cast-able back into original Rust value.
```rust use rust_dynamic::value::Value;
let mut value = Value::from(42).unwrap(); println!("Type of the stored value is {}", &value.typename()); println!("Dynamic value of the value is {:?}", &value.castinteger());
```
rust_dynamic crate supports a number of function-primitives that will take a raw value and return a wrapped Dynamic object.
| Function name | Description |
|---|---|
| Value::fromfloat() | Create dynamic object from f64 float number |
| Value::fromfloat32() | Create dynamic object from f32 float number |
| Value::frominteger() | Create dynamic object from i64 float number |
| Value::frominteger32() | Create dynamic object from i32 float number |
| Value::frombool() | Create dynamic object from boolean value |
| Value::fromstring() | Create dynamic object from Rust String |
| Value::fromstr() | Create dynamic object from Rust &str |
| Value::frombin() | Create dynamic object of type BINARY from Vec
There are generic function Value::from() that will automatically cast proper data type and ether return object or error message.
rust_dynamic supports a number of casting functions that will try to extract wrapped value from the object that holds dynamically-typed value.
| Function name | Description |
|---|---|
| Value::castfloat() | Return f64 number from FLOAT object |
| Value::castinteger() | Return i64 number from INTEGER object |
| Value::castbool() | Return boolean from BOOL object |
| Value::caststring() | Return String from STRING object |
| Value::castbin() | Return Vec
Example:
```rust use rust_dynamic::value::Value;
// First we create a dynamically-typed value from a raw static value let mut value = Value::from(42).unwrap();
// Then we can cast raw value back from the dynamic object let rawvalue = value.castinteger().unwrap() ```
Value attributes is a Vector of the values that stored in the Value object. You can assign any number of the Value objects stored as attributes of the Value object. Attributes are serailize-able and wrap-able.
| Function name | Description |
|---|---|
| Value.attr(Vec
Example:
rust
// Create object
let v = Value::from(42 as i64).unwrap()
// Set the attributes of the object
.attr(vec![Value::from(41.0 as f64).unwrap()])
// And merge some extra attributes. The first attribute now have a value of 42.0
.attr_merge(vec![Value::from(42.0 as f64).unwrap()]);
There are two serialization formats that rust_dynamic presently supports: JSON and Bincode.
| Function name | Description |
|---|---|
| Value::tojson() | Return JSON representation of dynamically-typed value |
| Value::tobinary() | Return Bincode representation of dynamically-typed value |
| Value::fromjson() | Takes string containing JSON representation of the dynamically-typed object and return re-created Value object |
| Value::frombinary() | Takes Vec
Example:
rust
// This call will create a new dynamic value
let mut data = Value::from(42 as i64).unwrap();
// This call will serialize object to Bincode format
let bin_out = data.to_binary().unwrap();
// This will re-create an object from it's Bincode representation
let mut data2 = Value::from_binary(bin_out).unwrap();
Crate rustdynamic is capable to export Value object to serdejson::value::Value
| Function name | Description | |---|---| | Value.asjsonvalue() | Function converts Value object to serde_json::value::Value |
Example:
rust
// This call will create a Value object of type PAIR and on-the-fly exports it
// to the serde_json::value::Value object that can be processed as any other Value object created by serde_json crate.
let v = Value::pair(Value::from_int(1), Value::from_int(2)).as_json_value();
While rust_dynamic crate is not strive to provide a full-featured functional interface to the dynamic values, some functionality that specific to a Functional programming has been implemented.
| Function name | Description | |---|---| | Value.bind() | Takes a reference to a function that accepts Value as a parameter, that function is called with passed current object and new Value object returned | | Value::bindvalues() | Takes a reference to a function that accepts two Values as a parameter, and two values. New Value object returned that is result of programmatic binding of the values | | Value.fmap() | Execute function to each element of the LIST or to the value and return new Value | | Value.mapfloat() | Execute function to each FLOAT element of the LIST or to the value and return new Value | | Value.push() | Ether add a new value to the list, or return a new Value | | Value.maybe() | Takes a function which is if returns true, Value.maybe() returns value, if false Value::none() | | Value::leftright() | Takes a function which is if returns true, and a references on two Values. Value::leftright() returns clone of first value, if function return true, second othewise | | Value::freduce() | Takes two parameters - function which takes two values and returning a single value and initial value, reducing dynamic value to a single value by applying function to all elements |
Example of mapping:
rust
// First, we define a function which will cast value of f64 and apply f64.sin() operation
fn comp_sin(value: Value) -> Value {
match value.data {
Val::F64(f_val) => {
return Value::from_float(f64::sin(f_val));
}
_ => return value,
}
}
// Then we create a single value object (map could be epplied to ether list or single value object)
let mut v = Value::from(42.0).unwrap();
// Now v contains result of computation
v = v.map_value(comp_sin);
Example of binding
rust
// First, let's create a "binding function" which will takes two Value objects and return a new Value
// In our example, simple sum() will be performed
fn sum_of_values(v1: Value, v2: Value) -> Value {
v1 + v2
}
// Then let's create two values
let v1 = Value::from(41.0 as f64).unwrap();
let v2 = Value::from(1.0 as f64).unwrap();
// Value referred as _s_ now contains value of 42.0
let s = Value::bind_values(sum_of_values, v1, v2);
Example of maybe()
rust
// First, we create a function that will check if v is float and is 42
fn if_value_is_42(v: &Value) -> bool {
if v.cast_float().unwrap() == 42.0 {
return true;
}
false
}
// And because it is, v is object of Value(42.0)
// Otherwise it would be Value::none()
let v = Value::from(42.0 as f64).unwrap()
.maybe(if_value_is_42);
Example of reducing
rust
// First, we shall create a list of the values
let mut v = Value::from_list(
vec![Value::from_float(41.0 as f64),
Value::from_float(1.0 as f64)]);
// Then reduce this list applying function that sums "accumulator" and current value
v = v.freduce(
|x: Value,y: Value| -> Value { x+y },
Value::from_float(0.0 as f64));
// This function shall return a single FLOAT value wrapping number 42.0
```rust use std::collections::HashMap;
// This call will create a key object. Key object can be of any supported type let key = Value::from(42.0 as f64).unwrap();
// Then we are creating a HashMap
let mut h: HashMap
// and store a key->value association h.insert(key, "value".to_string()); ```
rust
let mut c = 0.0;
// Let's create a object of LIST type and push two elements into list
let v = Value::list()
.push(Value::from(1.0 as f64).unwrap())
.push(Value::from(41.0 as f64).unwrap());
// We can iterate over dynamically-typed object
for i in v {
c += i.cast_float().unwrap();
}
In this example we are applying f64::sin function to all iterable values of the dynamically-typed object
rust
let mut v = Value::from(42.0).unwrap();
v = v.map_float(f64::sin);
At this moment, only FLOAT and INTEGER objects supported math operations.
rust
// Let's create x and y objects both of FLOAT type
let mut x = Value::from(1.0 as f64).unwrap();
let y = Value::from(41.0 as f64).unwrap();
// And perform math operation as usual
x = x + y;
At this moment, only STRING object supports that operation.
rust
// Let's create x and y objects both of STRING type
let mut x = Value::from("Hello").unwrap();
// Then perform operation as usual, x shall be a object of STRING type containing string "Hello world"
let y = Value::from(" world").unwrap();
x = x + y;
rust
// This call returns the object of type FLOAT
let val = Value::from("42").unwrap().conv(FLOAT).unwrap();
You can create an Applicative that is wrapping a function and apply a Value object with wrapped value to Applicative. For example:
```rust // First, as usual, we are defining function, that will be wrapped in Applicative fn compsin(value: Value) -> Value { match value.data { Val::F64(fval) => { return Value::fromfloat(f64::sin(fval)); } _ => return value, } }
// Create applicative and wrap a function let sin = Applicative::new(comp_sin); // Then apply a Value to a wrapped function let res = sin.apply(Value::from(42.0).unwrap()); ```