Returning iterators and combining them - rust

New to Rust and I have the following code for parsing a query in Actix that constructs a bunch of keys to query Redis:
#[derive(Debug, Deserialize)]
struct RouteRequest {
proto: String,
ids: Option<String>,
aliases: Option<String>
}
fn get_redis_keys_from_ids(ids: Option<&String>) -> Vec<String> {
match ids {
None => vec!{},
Some(s) => s.split(",").map(|s| "id:".to_owned() + s).collect()
}
}
fn get_redis_keys_from_aliases(aliases: Option<&String>) -> Vec<String> {
match aliases {
None => vec!{},
Some(s) => s.split(",").map(|s| "aid:".to_owned() + s).collect()
}
}
fn get_redis_keys(req: web::Form<RouteRequest>) -> Vec<String> {
let ids = get_redis_keys_from_ids(req.ids.as_ref());
let aliases = get_redis_keys_from_aliases(req.aliases.as_ref());
ids.into_iter().chain(aliases.into_iter()).collect()
}
Basically, if I have the following data:
RouteRequest {
ids: "foo,bar"
aliases: "baz,crux"
}
Then the vector returned by get_redis_keys() should be:
{"id:foo", "id:bar", "aid:baz", "aid:crux"}
What would be the cleanest and most efficient way of writing this code? Currently, I do this mapping of Option<String> to Option<&String> not sure if that's necessary, since I could just take ownership of the Option? I'm also returning Vecs from the helper functions, which causes unnecessary heap allocations.
In other words, how do I write this code in a better way avoiding unnecessary allocations? Trying to learn some Rust here.

Related

Rust, getting values from HashMap with enum

I'm trying make one HashMap with different types. I don't want to make two different HashMaps for specific data types.
My code is bellow:
use std::collections::HashMap;
#[derive(Debug)]
enum DataTypes {
String(String),
Bool(bool),
}
fn get_hashmap() -> Result<HashMap<String, DataTypes>, ()>{
let data = HashMap::from([
("password".to_string(), DataTypes::String("password".to_string())),
("username".to_string(), DataTypes::String("Fun username".to_string())),
("is_blocked".to_string(), DataTypes::Bool(true)),
("is_confirmed".to_string(), DataTypes::Bool(false)),
]);
Ok(data)
}
fn main() {
let data = get_hashmap().unwrap();
let keys = data.keys();
println!("Keys: {:?}", &keys);
for key in keys {
let result: Option<T> = match data.get(key).unwrap() {
DataTypes::Bool(value) => Some(value),
DataTypes::String(value) => Some(value),
_ => panic!("Error!"),
};
println!("Result of matching: {:?}", &result);
}
}
Like you can see I'm trying to maching Enums to getting their values. But i have some problem of data types.
My solution for this is wrap result of matching to Some struct. But still main problem is not resolved.
So I want to make result of matching in Option class to make available unwrap().
But I don't know how i can do that correctly...
I have two question:
Can i do this better?
How can I wrap let result: Option to working state?
Some feedback:
Don't include a _ default match case if you already handle all the options. It will hide future errors.
Don't name a variable DataTypes if every member is only a single datatype. Name it DataType.
result has to be a specific type. The whole point of the enum is that you can handle the different values separately, so combining them in a result type is pointless. Although you of course can keep result a DataType object and implement Debug/Display for it, which is how I will do it in my reworked code.
While you can query the key first and then again query the value in the loop, this is quite slow. You can iterate over key-value pairs right away. That way you avoid a lot of unwrap()s, which makes your code a lot less error-prone.
use std::collections::HashMap;
#[derive(Debug)]
enum DataType {
String(String),
Bool(bool),
}
fn get_hashmap() -> Result<HashMap<String, DataType>, ()> {
let data = HashMap::from([
(
"password".to_string(),
DataType::String("password".to_string()),
),
(
"username".to_string(),
DataType::String("Fun username".to_string()),
),
("is_blocked".to_string(), DataType::Bool(true)),
("is_confirmed".to_string(), DataType::Bool(false)),
]);
Ok(data)
}
fn main() {
let data = get_hashmap().unwrap();
for (key, value) in data {
println!("{}: {:?}", key, value);
match value {
DataType::Bool(value) => {
println!("\tValue was a bool: {}", value);
// do something if the value is a bool
}
DataType::String(value) => {
println!("\tValue was a string: {}", value);
// do something if the value is a string,
} /*
* Don't include a default case. That way the compiler
* will remind you to handle additional enum entries if
* you add them in the future.
* Adding a default case is only a good practice in languages
* where matching is not exhaustive.
*/
};
}
}
username: String("Fun username")
Value was a string: Fun username
is_confirmed: Bool(false)
Value was a bool: false
is_blocked: Bool(true)
Value was a bool: true
password: String("password")
Value was a string: password
Don't worry though, you don't need to use match everywhere you use this enum, otherwise you wouldn't win much compared to two separate hashmaps. You can, instead, define shared functionality for all enum entries, and hide the match inside of it. Like this:
use std::collections::HashMap;
#[derive(Debug)]
enum DataType {
String(String),
Bool(bool),
}
impl DataType {
fn do_something(&self) {
match self {
DataType::Bool(value) => {
println!("\tDo something with boolean '{}'!", value);
}
DataType::String(value) => {
println!("\tDo something with string {:?}!", value);
}
};
}
}
fn get_hashmap() -> Result<HashMap<String, DataType>, ()> {
let data = HashMap::from([
(
"password".to_string(),
DataType::String("password".to_string()),
),
(
"username".to_string(),
DataType::String("Fun username".to_string()),
),
("is_blocked".to_string(), DataType::Bool(true)),
("is_confirmed".to_string(), DataType::Bool(false)),
]);
Ok(data)
}
fn main() {
let data = get_hashmap().unwrap();
for (key, value) in data {
println!("{}: {:?}", key, value);
value.do_something();
}
}
is_confirmed: Bool(false)
Do something with boolean 'false'!
password: String("password")
Do something with string "password"!
is_blocked: Bool(true)
Do something with boolean 'true'!
username: String("Fun username")
Do something with string "Fun username"!
If your goal is to add serialization/deserialization to your struct (as you seem to implement manually here), let me hint you towards serde, which already takes care of the majority of serialization for free.
Like in this example (which may or may not be how your struct looks like) that serializes your struct to and from JSON:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
username: String,
password: String,
is_blocked: bool,
is_confirmed: bool,
}
fn main() {
let user = User {
username: "Fun username".to_string(),
password: "password".to_string(),
is_blocked: true,
is_confirmed: false,
};
let user_serialized = serde_json::to_string(&user).unwrap();
println!("Serialized: {}", user_serialized);
let user_deserialized: User = serde_json::from_str(&user_serialized).unwrap();
println!("Name: {}", user_deserialized.username);
}
Serialized: {"username":"Fun username","password":"password","is_blocked":true,"is_confirmed":false}
Name: Fun username

Derive macro generation

I'm making my own Serializable trait, in the context of a client / server system.
My idea was that the messages sent by the system is an enum made by the user of this system, so it can be customize as needed.
Too ease implementing the trait on the enum, I would like to use the #[derive(Serializable)] method, as implementing it is always the same thing.
Here is the trait :
pub trait NetworkSerializable {
fn id(&self) -> usize;
fn size(&self) -> usize;
fn serialize(self) -> Vec<u8>;
fn deserialize(id: usize, data: Vec<u8>) -> Self;
}
Now, I've tried to look at the book (this one too) and this example to try to wrap my head around derive macros, but I'm really struggling to understand them and how to implement them. I've read about token streams and abstract trees, and I think I understand the basics.
Let's take the example of the id() method : it should gives a unique id for each variant of the enum, to allow headers of messages to tell which message is incoming.
let's say I have this enum as a message system :
enum NetworkMessages {
ErrorMessage,
SpawnPlayer(usize, bool, Transform), // player id, is_mine, position
MovePlayer(usize, Transform), // player id, new_position
DestroyPlayer(usize) // player_id
}
Then, the id() function should look like this :
fn id(&self) -> usize {
match &self {
&ErrorMessage => 0,
&SpawnPlayer => 1,
&MovePlayer => 2,
&DestroyPlayer => 3,
}
}
Here was my go with writting this using a derive macro :
#[proc_macro_derive(NetworkSerializable)]
pub fn network_serializable_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_network_serializable_macro(&ast)
}
fn impl_network_serializable_macro(ast: &syn::DeriveInput) -> TokenStream {
// get enum name
let ref name = ast.ident;
let ref data = ast.data;
let (id_func, size_func, serialize_func, deserialize_func) = match data {
// Only if data is an enum, we do parsing
Data::Enum(data_enum) => {
// Iterate over enum variants
let mut id_func_internal = TokenStream2::new();
let mut variant_id: usize = 0;
for variant in &data_enum.variants {
// add the branch for the variant
id_func_internal.extend(quote_spanned!{
variant.span() => &variant_id,
});
variant_id += 1;
}
(id_func_internal, (), (), ())
}
_ => {(TokenStream2::new(), (), (), ())},
};
let expanded = quote! {
impl NetworkSerializable for #name {
// variant_checker_functions gets replaced by all the functions
// that were constructed above
fn size(&self) -> usize {
match &self {
#id_func
}
}
/*
#size_func
#serialize_func
#deserialize_func
*/
}
};
expanded.into()
}
So this is generating quite a lot of errors, with the "proc macro NetworkSerializable not expanded: no proc macro dylib present" being first. So I'm guessing there a lot of misunderstaning from my part in here.

Access the value of an enum variant

I am working on some language bindings to Arrayfire using the arrayfire-rust crate.
Arrayfire has a typed struct Array<T> which represents a matrix. All acceptable types implement the HasAfEnum trait. This trait has a number of associated types, whose values are not the same for the types that implement this trait.
Since I need a reference to the array in a Rwlock for safe language interop, I have defined the following struct:
pub struct ExAfRef(pub RwLock<ExAfArray>);
impl ExAfRef {
pub fn new(slice: &[u8], dim: Dim4, dtype: ExAfDType) -> Self {
Self(RwLock::new(ExAfArray::new(slice, dim, dtype)))
}
pub fn value(&self) -> ExAfArray {
match self.0.try_read() {
Ok(refer) => (*refer),
Err(_) => unreachable!(),
}
}
}
which is contained by a struct:
pub struct ExAf {
pub resource: ResourceArc<ExAfRef>,
}
impl ExAf {
pub fn new(slice: &[u8], dim: Dim4, dtype: ExAfDType) -> Self {
Self {
resource: ResourceArc::new(ExAfRef::new(slice, dim, dtype)),
}
}
// This function is broken
pub fn af_value<T: HasAfEnum>(&self) -> &Array<T> {
self.resource.value().value()
}
}
With the help of the following enum:
pub enum ExAfArray {
U8(Array<u8>),
S32(Array<i32>),
S64(Array<i64>),
F32(Array<f32>),
F64(Array<f64>),
}
impl ExAfArray {
pub fn new(slice: &[u8], dim: Dim4, dtype: ExAfDType) -> Self {
let array = Array::new(slice, dim);
match dtype {
ExAfDType::U8 => ExAfArray::U8(array),
ExAfDType::S32 => ExAfArray::S32(array.cast::<i32>()),
ExAfDType::S64 => ExAfArray::S64(array.cast::<i64>()),
ExAfDType::F32 => ExAfArray::F32(array.cast::<f32>()),
ExAfDType::F64 => ExAfArray::F64(array.cast::<f64>()),
}
}
// This function is broken
pub fn value<T: HasAfEnum>(&self) -> &Array<T> {
// match self {
// ExAfArray::U8(array) => array,
// ExAfArray::S32(array) => array,
// ExAfArray::S64(array) => array,
// ExAfArray::F32(array) => array,
// ExAfArray::F64(array) => array,
// }
if let ExAfArray::U8(array) = self {
return array;
} else if let ExAfArray::S32(array) = self {
return array;
} else if let ExAfArray::S64(array) = self {
return array;
} else if let ExAfArray::F32(array) = self {
return array;
} else {
let ExAfArray::F64(array) = self;
return array;
}
}
pub fn get_type(&self) -> ExAfDType {
match self {
ExAfArray::U8(array) => ExAfDType::U8,
ExAfArray::S32(array) => ExAfDType::S32,
ExAfArray::S64(array) => ExAfDType::S64,
ExAfArray::F32(array) => ExAfDType::F32,
ExAfArray::F64(array) => ExAfDType::F64,
}
}
}
I have used an enum because generic structs are not supported in my language-interop "framework" and because the HasAfEnum trait has associated types (hence dynamic dispatch using dyn is not viable (at least to my knowledge)).
This has worked fine for initializing new arrays.
However when I need to apply some operation on an array, I need to be able to access the value stored by the enum variant. However I am unable to write a type signature for a function to access the value, as dynamic dispatch is not usable and generics are too boilerplate.
Since all variants are tuples, is there some way I can access the value of the tuple variant using a built-in enum feature?
EDIT:
I am using rustler
In short, no there is not a way to do what you seem to be trying to do in Rust presently.
Your functions are broken because you are trying to use generics orthogonally to how they work. When a generic function is called in Rust, the caller fills in the type parameters, not the callee. However, your enum in a sense "knows" what the concrete array type is, so only it can determine what that type parameter is supposed to be. If this mismatch is blocking your progress, this usually calls for a reconsideration of your code structure.
This also explains why there is no built-in enum method that does what you're trying to do. That method would run into the same issue as your value method. When you want to inspect the contents of an enum in Rust, you need to pattern match on it.
There is at least one way to try to accomplish your goal, but I would not really recommend it. One change that makes the code closer to being viable is by passing a closure into the function to make the modification, (the syntax below is not currently valid Rust but it gets the idea across):
pub fn modify<'a, F>(&'a self, op: F)
where
F: for<T: HasAfEnum> FnOnce(&'a Array<T>)
{
// This looks repetitive, but the idea is that in each branch
// the type parameter T takes on the appropriate type for the variant
match self {
ExAfArray::U8(array) => op(array),
ExAfArray::S32(array) => op(array),
ExAfArray::S64(array) => op(array),
ExAfArray::F32(array) => op(array),
ExAfArray::F64(array) => op(array),
}
}
Unfortunately the for<T> FnTrait(T) syntax does not exist yet and I'm not even sure if there's a proposal for it to be added. This can be worked around through a macro:
pub(crate) fn call_unary<F, T, U>(arg: T, f: F) -> U
where F: FnOnce(T) -> U {
f(arg)
}
macro_rules! modify {
($ex_af_array:expr, $op:expr) => {
match &$ex_af_array {
ExAfArray::U8(array) => call_unary(array, $op),
ExAfArray::S32(array) => call_unary(array, $op),
ExAfArray::S64(array) => call_unary(array, $op),
ExAfArray::F32(array) => call_unary(array, $op),
ExAfArray::F64(array) => call_unary(array, $op),
}
};
}
The call_unary helper is needed to ensure type inference works properly. ($op)(array) will fail to compile when the types of the arguments to $op need to be inferred.
Now this solution mostly covers the functionality that for<T> FnTrait(T) would provide, but it's not very clean code (especially after the macro body is sanitized), and the compiler errors will be poor if the macro is misused.

Function that generates a HashMap of Enum variants

I'm working with apollo_parser to parse a GraphQL query. It defines an enum, apollo_parser::ast::Definition, that has several variants including apollo_parser::ast::OperationDefintion and apollo_parser::ast::FragmentDefinition. I'd like to have a single Trait I can apply to apollo_parser::ast::Definition that provides a function definition_map that returns a HashMap mapping the operation name to the variant instance.
I've got as far as the trait, but I don't know how to implement it. Also, I don't know how to constrain T to be a variant of Definition.
trait Mappable {
fn definition_map<T>(&self) -> HashMap<String, T>;
}
EDIT:
Here's a Rust-ish pseudocode implementation.
impl Mappable for Document {
fn definition_map<T>(&self) -> HashMap<String, T> {
let defs = Vec<T> = self.definitions
.filter_map(|def: Definition| match def {
T(foo) => Some(foo),
_ => None
}).collect();
let map = HashMap::new();
for def: T in definitions {
map.insert(def.name(), def);
}
map
}
}
and it would output
// From a document consisting of OperationDefinitions "operation1" and "operation2"
// and FragmentDefinitons "fragment1" and "fragment2"
{
"operation1": OperationDefinition(...),
"operation2": OperationDefinition(...),
}
{
"fragment1": FragmentDefinition(...),
"fragment2": FragmentDefinition(...)
}
I don't know how to constrain T to be a variant of Definition.
There is no such thing in Rust. There's the name of the variant and the name of the type contained within that variant, there is no relationship between the two. The variants can be named whatever they want, and multiple variant can contain the same type. So there's no shorthand for pulling a T out of an enum which has a variant with a T.
You need to make your own trait that says how to get a T from a Definition:
trait TryFromDefinition {
fn try_from_def(definition: Definition) -> Option<Self> where Self: Sized;
fn name(&self) -> String;
}
And using that, your implementation is simple:
impl Mappable for Document {
fn definition_map<T: TryFromDefinition>(&self) -> HashMap<String, T> {
self.definitions()
.filter_map(T::try_from_def)
.map(|t| (t.name(), t))
.collect()
}
}
You just have to define TryFromDefinition for all the types you want to use:
impl TryFromDefinition for OperationDefinition {
fn try_from_def(definition: Definition) -> Option<Self> {
match definition {
Definition::OperationDefinition(operation) => Some(operation),
_ => None,
}
}
fn name(&self) -> String {
self.name().unwrap().ident_token().unwrap().text().into()
}
}
impl TryFromDefinition for FragmentDefinition {
fn try_from_def(definition: Definition) -> Option<Self> {
match definition {
Definition::FragmentDefinition(operation) => Some(operation),
_ => None,
}
}
fn name(&self) -> String {
self.fragment_name().unwrap().name().unwrap().ident_token().unwrap().text().into()
}
}
...
Some of this could probably be condensed using macros, but there's no normalized way that I can tell to get a name from a definition, so that would still have to be custom per type.
You should also decide how you want to handle definitions that don't have a name; you'd probably want to return Option<String> to avoid all those .unwrap()s, but I don't know how you'd want to put that in your HashMap.
Without knowing your whole workflow, I might suggest a different route instead:
struct Definitions {
operations: HashMap<String, OperationDefinition>,
fragments: HashMap<String, FragmentDefinition>,
...
}
impl Definitions {
fn from_document(document: &Document) -> Self {
let mut operations = HashMap::new();
let mut fragments = HashMap::new();
...
for definition in document.definitions() {
match definition {
Definition::OperationDefinition(operation) => {
let name: String = operation.name().unwrap().ident_token().unwrap().text().into();
operations.insert(name, operation);
},
Definition::FragmentDefinition(fragment) => {
let name: String = fragment.fragment_name().unwrap().name().unwrap().ident_token().unwrap().text().into();
fragments.insert(name, fragment);
},
...
}
}
Definitions {
operations,
fragments,
...
}
}
}

Make non-Future Rust function into a Future function?

I inherited a Rust application and I wish to make a small modification to it. Presently, it retrieves records from Cassandra in the following way using Futures:
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Item {
pub a: String,
pub b: f64,
}
#[derive(Debug)]
pub enum DataSetError {
CassandraError(cassandra_cpp::Error),
}
pub type Result<T> = std::result::Result<T, DataSetError>;
pub fn select_cass_items(
session: &Session,
a: String,
) -> impl Future<Output = Result<Vec<Item>>> + Unpin {
let table = envmnt::get_or("TABLE", "ab_table");
let mut statement = stmt!(&("SELECT a, b FROM ".to_owned() + &table + " WHERE a = ?"));
statement.bind(0, a).unwrap();
session.execute(&statement).map(|result| {
result
.map(|rows| {
rows.iter()
.map(|row| Item {
a: row.get_by_name("a").unwrap(),
b: row.get_by_name("b").unwrap(),
})
.collect()
})
.map_err(|e| {
warn!("[select_cass_items] {:?}", e);
DataSetError::CassandraError(e)
})
})
}
I want to add the option of doing the same thing but from Parquet files. I have written a simple non-Future function (below) that does the equivalent reading/filtering operation as the Cassandra function. I've verified it works as intended.
pub fn read_parquet_file (
a: String)
-> Vec<Item> {
let reader = SerializedFileReader::try_from("/path/to/file.parquet".to_string()).unwrap();
let iter = reader.get_row_iter(None).unwrap();
iter.filter_map(|row| {
if row.get_string(0).unwrap() == &a {
Some(Item {
a: row.get_string(0).unwrap().to_string(),
b: row.get_double(1).unwrap(),
})
}
else {
None
}
}).collect::<Vec<_>>()
}
The question is: how do I convert the non-Future Parquet function to be a drop-in replacement for the Future Cassandra function? I see that the cassandra_cpp crate supports Futures, but the parquet create does not. Surely there must be a way to do this? However, I'm a Rust newbie, and I can't find any examples close enough to what I want to be able to mogrify my work into what I need. I've tried various things but they've all been dead ends, and aren't worth sharing.
Thank you!

Resources