Rust Vector Mapping with Strings - rust

I have to structs with fields of type String that I want to map to each other.
struct A {
name: String,
}
struct B {
id: String
}
let a_list = vec![A {name: "a1".to_string()}, A {name: "a2".to_string()}];
let b_list = a_list.iter().map(|a| B {id: a.name}).collect::<Vec<_>>();
When I execute this, I get the following error because of missing Copy traits for the field of type String.
14 | let b_list = a_list.iter().map(|a| B {id: a.name}).collect::<Vec<_>>();
| ^^^^^^ move occurs because `a.name` has type `String`, which does not implement the `Copy` trait
My current workaround to this issue is that in the mapping, I temporarily convert the field of type String into &str and then back again to String, which appears kind of hacky to me:
let b_list = a_list.iter().map(|a| B {id: a.name.as_str().to_string()}).collect::<Vec<_>>();
I wonder if there is a more elegant solution to this?

Depending on wether you still need a_list after the conversion you could either:
// if you still need a_list
let b_list: Vec<B> = a_list.iter().map(|a| B{id: a.name.clone()}).collect();
// if you don't need a_list anymore
let b_list: Vec<B> = a_list.into_iter().map(|a| B{id: a.name}).collect();

Related

Hashmap with enum values

Rust Newbie.
I'd like to create a hashmap that contains values of different types. I got as far as shown, and I can store the values, but I cannot cast them back to the original time when reading them. I'm sure I'm missing something basic, but I'm still struggling with the enum concept in Rust.
#[derive(Debug)]
struct My1 { value: i32 }
#[derive(Debug)]
struct My2 { value: String }
#[derive(Debug)]
enum MyValueType {
MyOne(Vec<My1>),
MyTwo(Vec<My2>)
}
fn main() {
use std::collections::HashMap;
let mut map: HashMap<&str, MyValueType> = HashMap::new();
let a1 = vec!(My1 { value: 100 });
let a2 = vec!(My2 { value: "onehundred".into() });
map.insert("one", MyValueType::MyOne(a1));
map.insert("two", MyValueType::MyTwo(a2));
//let b: &Vec<My1> = map.get("one").unwrap().into(); // err
for (key, value) in &map {
println!("{}: {:?}", key, value);
}
let k1: Vec<My1> = *map.get("one").unwrap().into(); // err: type annotation needed
let k2: Vec<My2> = *map.get("two").unwrap().into(); // err: type annotation needed
}
How should I implement this so I can cast the value of type MyValueType back to Vec or Vec as the case may be? Or am I fundamentally wrong on how I'm setting this up in general?
Starting with:
let v = map.get("one");
The hash map will return an option of the enum (Option<MyValueType>). After unwrapping the option, you’re left with the enum.
let v = map.get("one"); // v is MyValueType (specifically MyOne)
This enum has one of the possible values of MyOne or MyTwo, but we don’t yet know which (more specifically — the compiler doesn’t know, even if we can tell just by looking that it’s MyOne). If you want to reach in to MyOne or MyTwo and grab one of the Vecs that are stored there, you need to match against the enum. For example:
match map.get("one").unwrap() {
MyValueType::MyOne(vector) => {
// do something
},
MyValurType::MyTwo => panic!(“unexpected)
}
This intentionally forces you to check that the enum is the value you are expecting before you are able to access to the data within. Typically you won’t know the exact type of the enum when you are writing code (otherwise why use an enum!) which is why this might seem a bit verbose.

Why does returning a generic object show expected type parameter `T`, found struct `Vec`?

I defined a function for postgresql pagination query like this:
pub fn fav_music_query<T>() -> Paginated<T> {
use crate::model::diesel::rhythm::rhythm_schema::favorites::dsl::*;
let connection = config::establish_music_connection();
let query = favorites.filter(like_status.eq(1)).paginate(1).per_page(10);
let query_result = query.load_and_count_pages::<Favorites>(&connection).unwrap();
let page_result = Paginated{
query: query_result.0,
page: 1,
per_page: 10,
is_sub_query: false
};
return page_result;
}
when I use this code to invoke the function:
let dashboards = fav_music_query::<Vec<Favorites>>();
it shows this error:
error[E0308]: mismatched types
--> src/service/home/home_service.rs:18:12
|
6 | pub fn fav_music_query<T>() -> Paginated<T> {
| - ------------ expected `Paginated<T>` because of return type
| |
| this type parameter
...
18 | return page_result;
| ^^^^^^^^^^^ expected type parameter `T`, found struct `Vec`
|
= note: expected struct `Paginated<T>`
found struct `Paginated<Vec<Favorites>>`
what should I do to fix it? Vec<> not a T generic type?
Vec<?> would be a valid option for T, but the issue is that it always attempts to return a Vec<Favorites> regardless of if the requested T is a Vec or not.
It is complaining because the function signature declares that it returns a generic, but the function implementation always attempts to return the same type.
// For example, according to the function signature I should be able to do this
let bar: Foo = fav_music_query<Foo>();
Now what I assume you want to do is have the function return a generic Paginated result. That isn't too difficult to do, we just need to replace the hard coded type with our generic and tweak the signature to match the result of load_and_count_pages() when we request our generic T from it. That being said, you will probably run into some errors with load_and_count_pages having bounds on the generic T which you will have to apply to the function signature fav_music_query<T: SomeBounds + OtherBounds>().
// Tweak the signature to follow expected result of load_and_count_pages
pub fn fav_music_query<T>() -> Paginated<Vec<T>> {
use crate::model::diesel::rhythm::rhythm_schema::favorites::dsl::*;
let connection = config::establish_music_connection();
let query = favorites.filter(like_status.eq(1)).paginate(1).per_page(10);
// Change Faborites to T so the generic is used instead of a hardcoded type
let query_result = query.load_and_count_pages::<T>(&connection).unwrap();
let page_result = Paginated{
query: query_result.0,
page: 1,
per_page: 10,
is_sub_query: false
};
return page_result;
}
Alternatively, if you only want Paginated<Vec<Favorites>>, you could just remove the generic entirely and it should work just fine.
pub fn fav_music_query() -> Paginated<Vec<Favorites>> { ... }

Is there a way to split up a variable inside an Option without having to use if statements?

I want the code below to work, but since map() takes ownership of Option, and there doesn't seem to be a clone() function for Option, the following doesn't compile.
fn main() {
struct ItemA {
a: String,
b: String,
}
let foo = Some(ItemA {
a: "A String".to_owned(),
b: "B String".to_owned(),
});
// OR
// let foo = None;
let opA: Option<String> = foo.map(|c| c.a);
let opB: Option<String> = foo.map(|c| c.b);
}
error[E0382]: use of moved value: `foo`
--> src/main.rs:15:31
|
14 | let opA: Option<String> = foo.map(|c| c.a);
| --- value moved here
15 | let opB: Option<String> = foo.map(|c| c.b);
| ^^^ value used here after move
|
= note: move occurs because `foo` has type `std::option::Option<main::ItemA>`, which does not implement the `Copy` trait
It would be nice if opA can take ownership of ItemA.a (so it doesn't have to clone the strings), and opB can take ownership of ItemA.b
Is this possible to do without having to use if statements to check if the Option is Some or None, unwrapping, and wrapping it back up individually.
You can use map_or_else.
let (opA, opB) = foo.map_or_else(
|| (None, None),
|c| (Some(c.a), Some(c.b))
);
The first function is called if foo is None, and returns two Nones. The second function is called if foo is Some and splits the members into a tuple.
Of course, this doesn't really save you much over a simple match, and will probably be harder to follow.
let (opA, opB) = match foo {
None => (None, None),
Some(c) => (Some(c.a), Some(c.b))
};
By the way, Option does implement Clone, but it requires that the contained type implements Clone.
[T]here doesn't seem to be a clone() function for Option [...]
That's wrong, there is, if the inner type implements clone as well. So in your case, just add #[derive(Clone)] to your struct ItemA.
Next, you can use as_ref to create an Option<&T> and then use map.
let op_a = foo.as_ref().map(|c| &c.a);
let op_b = foo.as_ref().map(|c| &c.b);
println!("{:?}", op_a);
println!("{:?}", op_b);

How do I store a struct in two places?

I'm doing Advent Of Code Day 7 in Rust. I have to parse a tree out of order like so:
a(10)
c(5) -> a, b
b(20)
That says c is the root with a and b as its children.
I handle this by parsing each line, making an object, and storing it in a hash by name. If it shows up later as a child, like a, I can use that hash to lookup the object and apply it as a child. If it shows up as a child before being defined, like b, I can create a partial version and update it via the hash. The above would be something like:
let mut np = NodeParser{
map: HashMap::new(),
root: None,
};
{
// This would be the result of parsing "a(10)".
{
let a = Node{
name: "a".to_string(),
weight: Some(10),
children: None
};
np.map.insert( a.name.clone(), a );
}
// This is the result of parsing "c(5) -> a, b".
// Note that it creates 'b' with incomplete data.
{
let b = Node{
name: "b".to_string(),
weight: None,
children: None
};
np.map.insert("b".to_string(), b);
let c = Node{
name: "c".to_string(),
weight: Some(5),
children: Some(vec![
*np.map.get("a").unwrap(),
// ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
*np.map.get("b").unwrap()
// ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
])
};
np.map.insert( c.name.clone(), c );
}
// Parsing "b(20)", it's already seen b, so it updates it.
// This also updates the entry in c.children. It avoids
// having to search all nodes for any with b as a child.
{
let mut b = np.map.get_mut( "b" ).unwrap();
b.weight = Some(20);
}
}
I might want to look up a node and look at its children.
// And if I wanted to look at the children of c...
let node = np.map.get("c").unwrap();
for child in node.children.unwrap() {
// ^^^^ cannot move out of borrowed content
println!("{:?}", child);
}
Rust does not like this. It doesn't like that both NodeParser.map and Node.children own a node.
error[E0507]: cannot move out of borrowed content
--> /Users/schwern/tmp/test.rs:46:21
|
46 | *np.map.get("a").unwrap(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
error[E0507]: cannot move out of borrowed content
--> /Users/schwern/tmp/test.rs:49:21
|
49 | *np.map.get("b").unwrap()
| ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
It doesn't like that the for loop is trying to borrow the node to iterate because I've already borrowed the node from the NodeParser that owns it.
error[E0507]: cannot move out of borrowed content
--> /Users/schwern/tmp/test.rs:68:18
|
68 | for child in node.children.unwrap() {
| ^^^^ cannot move out of borrowed content
I think I understand what I'm doing wrong, but I'm not sure how to make it right.
How should I construct this to make the borrower happy? Because of the way NodeParser.map and Node.children must be linked, copying is not an option.
Here is the code to test with. In the real code both Node and NodeParser have implementations and methods.
One option is unsafe code ... but I would suggest avoiding that if you're using the Advent of Code to learn idiomatic Rust and not just drop all the safety its trying to give you.
Another option is to reference count the Node instances so that the borrow checker is happy and the compiler knows how to clean things up. The std::rc::Rc type does this for you ... and essentially every call to clone() just increments a reference count and returns a new Rc instance. Then every time an object is dropped, the Drop implementation just decrements the reference count.
As for the iteration .. for x in y is syntactic sugar for for x in y.into_iter(). This is attempting to move the contents of children out of node (notice in the IntoIterator trait, into_iter(self) takes ownership of self). To rectify this, you can ask for a reference instead when iterating, using for x in &y. This essentially becomes for x in y.iter(), which does not move the contents.
Here are these suggestions in action.
use std::collections::HashMap;
use std::rc::Rc;
struct NodeParser {
map: HashMap<String, Rc<Node>>,
root: Option<Node>,
}
#[derive(Debug)]
struct Node {
name: String,
children: Option<Vec<Rc<Node>>>,
}
fn main() {
let mut np = NodeParser{
map: HashMap::new(),
root: None,
};
let a = Rc::new(Node{ name: "a".to_string(), children: None });
np.map.insert( a.name.clone(), a.clone() );
let b = Rc::new(Node{ name: "b".to_string(), children: None });
np.map.insert( b.name.clone(), b.clone() );
let c = Rc::new(Node{
name: "c".to_string(),
children: Some(vec![a, b])
});
np.map.insert( c.name.clone(), c.clone() );
let node = np.map.get("c").unwrap();
for child in &node.children {
println!("{:?}", child);
}
}
EDIT: I will expand on my comment here. You can use lifetimes here too if you want, but I'm concerned that the lifetime solution will work against the MCVE and won't work once applied to the actual problem the OP (not just of this question... others as well) actually has. Lifetimes are tricky in Rust and small things like re-ordering the instantiation of variables to allow the lifetime solution can throw people off. My concern being they will run into lifetime issues and therefore the answers won't be appropriate to their actual situation even if it works for the MCVE. Maybe I overthink that though..

error: use of moved value - should I use "&" or "mut" or something else?

My code:
enum MyEnum1 {
//....
}
struct Struct1 {
field1: MyEnum1,
field2: String
}
fn fn1(a: Struct1, b: String, c: String) -> String {
let let1 = fn2(a.field1);
let let2 = fn3(let1, b, c);
format!("{} something 123 {}", let1, let2)
}
fn fn2(a: MyEnum1) -> String {
//....
}
fn fn3(a: MyEnum1, b: Struct1) -> String {
//....
}
error: use of moved value: `a.field1`
error: use of moved value: `let1`
How can I fix them? Should I add & to the parameters of 'fn2andfn3? Ormut`? I can't understand the idea of how to fix these kind of errors.
These errors come from the most important concept in Rust - ownership. You should read the official book, especially the chapter on ownership - this would help you understand "how tho fix this kind of errors".
In short, specifically in your code, the problem is that String is a non-copyable type, that is, String values are not copied when passed to functions or assigned to local variables, they are moved. This means that wherever they were before, they are not accessible from there anymore.
Let's look at your function:
enum MyEnum1 {
//....
}
struct Struct1 {
field1: MyEnum1,
field2: String
}
fn fn1(a: Struct1, b: String, c: String) -> String {
let let1 = fn2(a.field1);
let let2 = fn3(let1, b, c);
format!("{} something 123 {}", let1, let2)
}
fn fn2(a: MyEnum1) -> String {
//....
}
All types here are not automatically copyable (they don't implement Copy trait). String is not copyable because it is a heap-allocated string and copying would need a fresh allocation (an expensive operation which better be not implicit), MyEnum1 is not copyable because it does not implement Copy (with #[deriving(Copy, Clone)], for example; and it is unclear if it can be made copyable because you didn't provide its variants), and Struct1 is not copyable because it contains non-copyable types.
In fn1 you invoke fn2, passing it field1 and getting a String back. Then you immediately passes this String to fn3. Because String is not copyable, whatever is stored in let1 is moved into the called function, making let1 inaccessible. This is what "use of moved value" error is about. (The code you provided can't cause "use of moved value: a.field1" error, so it probably came from the parts you omitted, but the basic idea is absolutely the same)
There are several ways to fix these errors, but the most natural and common one is indeed to use borrowed references. In general if you only want to read some non-copyable value in a function you should pass it there by reference:
fn use_myenum(e: &MyEnum1)
For strings and arrays, however, the better way would be to pass slices:
fn use_str(s: &str) { ... }
let s: String = ...;
use_str(&s); // here &String is automatically converted to &str
You can find more on slices in the book, here.

Resources