The Rust Book: HashMap Challenge

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
7
down vote
favorite
I'm working my way through the Rust Book:
Ch.8 Challenge:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in the company. For example, âÂÂAdd Sally to Engineeringâ or âÂÂAdd Amir to SalesâÂÂ. Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
use std::collections::HashMap;
#[derive(Debug)]
enum Command
Add(String, String),
List,
Unknown
fn command_filter(input: String) -> Command
let mut command_string = input.split_whitespace();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add(name.to_string(), dept.to_string()),
_ => Command::Unknown
,
Some("List") => Command::List,
_ => Command::Unknown
fn process_command(command: Command, employee_map: &mut HashMap<String, String>)
match command
Command::Add(name, dept) =>
employee_map.insert(name, dept);
()
,
Command::List =>
for (name, dept) in employee_map
println!("Name: Dept: ", name, dept);
;
,
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employee: HashMap<String, String> = HashMap::new();
let command = command_filter(String::from("Add John to Eng"));
process_command(command, &mut employee);
let command = command_filter(String::from("Add Devon to Bio"));
process_command(command, &mut employee);
let command = command_filter(String::from("List"));
process_command(command, &mut employee);
As with most devs starting to learn Rust I am finding difficulty with borrowing. I do not come from a C/C++ background and so I'm also finding pointers versus actual object a little tricky too.
Some specific thoughts:
- I am suspicious that match statements inside match statements is not good practice.
- The
Command::Addbranch inprocess_commandfeels a little hacky with the()return. Edit: After thinking more about this problem, I think if I wrapped the branch responses inOk()then I could return aResultfrom that function.
Do you have any feedback?
beginner rust
add a comment |Â
up vote
7
down vote
favorite
I'm working my way through the Rust Book:
Ch.8 Challenge:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in the company. For example, âÂÂAdd Sally to Engineeringâ or âÂÂAdd Amir to SalesâÂÂ. Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
use std::collections::HashMap;
#[derive(Debug)]
enum Command
Add(String, String),
List,
Unknown
fn command_filter(input: String) -> Command
let mut command_string = input.split_whitespace();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add(name.to_string(), dept.to_string()),
_ => Command::Unknown
,
Some("List") => Command::List,
_ => Command::Unknown
fn process_command(command: Command, employee_map: &mut HashMap<String, String>)
match command
Command::Add(name, dept) =>
employee_map.insert(name, dept);
()
,
Command::List =>
for (name, dept) in employee_map
println!("Name: Dept: ", name, dept);
;
,
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employee: HashMap<String, String> = HashMap::new();
let command = command_filter(String::from("Add John to Eng"));
process_command(command, &mut employee);
let command = command_filter(String::from("Add Devon to Bio"));
process_command(command, &mut employee);
let command = command_filter(String::from("List"));
process_command(command, &mut employee);
As with most devs starting to learn Rust I am finding difficulty with borrowing. I do not come from a C/C++ background and so I'm also finding pointers versus actual object a little tricky too.
Some specific thoughts:
- I am suspicious that match statements inside match statements is not good practice.
- The
Command::Addbranch inprocess_commandfeels a little hacky with the()return. Edit: After thinking more about this problem, I think if I wrapped the branch responses inOk()then I could return aResultfrom that function.
Do you have any feedback?
beginner rust
To get better feedback I suggest you add some comments of your own. What do you think might be lacking? Are there things you're not quite sure of? Which part did you find the hardest to implement?
â Rene Saarsoo
May 3 at 12:30
Hi, thanks. I'll add some of my thoughts!
â Mungo Dewar
May 3 at 19:39
add a comment |Â
up vote
7
down vote
favorite
up vote
7
down vote
favorite
I'm working my way through the Rust Book:
Ch.8 Challenge:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in the company. For example, âÂÂAdd Sally to Engineeringâ or âÂÂAdd Amir to SalesâÂÂ. Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
use std::collections::HashMap;
#[derive(Debug)]
enum Command
Add(String, String),
List,
Unknown
fn command_filter(input: String) -> Command
let mut command_string = input.split_whitespace();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add(name.to_string(), dept.to_string()),
_ => Command::Unknown
,
Some("List") => Command::List,
_ => Command::Unknown
fn process_command(command: Command, employee_map: &mut HashMap<String, String>)
match command
Command::Add(name, dept) =>
employee_map.insert(name, dept);
()
,
Command::List =>
for (name, dept) in employee_map
println!("Name: Dept: ", name, dept);
;
,
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employee: HashMap<String, String> = HashMap::new();
let command = command_filter(String::from("Add John to Eng"));
process_command(command, &mut employee);
let command = command_filter(String::from("Add Devon to Bio"));
process_command(command, &mut employee);
let command = command_filter(String::from("List"));
process_command(command, &mut employee);
As with most devs starting to learn Rust I am finding difficulty with borrowing. I do not come from a C/C++ background and so I'm also finding pointers versus actual object a little tricky too.
Some specific thoughts:
- I am suspicious that match statements inside match statements is not good practice.
- The
Command::Addbranch inprocess_commandfeels a little hacky with the()return. Edit: After thinking more about this problem, I think if I wrapped the branch responses inOk()then I could return aResultfrom that function.
Do you have any feedback?
beginner rust
I'm working my way through the Rust Book:
Ch.8 Challenge:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in the company. For example, âÂÂAdd Sally to Engineeringâ or âÂÂAdd Amir to SalesâÂÂ. Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
use std::collections::HashMap;
#[derive(Debug)]
enum Command
Add(String, String),
List,
Unknown
fn command_filter(input: String) -> Command
let mut command_string = input.split_whitespace();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add(name.to_string(), dept.to_string()),
_ => Command::Unknown
,
Some("List") => Command::List,
_ => Command::Unknown
fn process_command(command: Command, employee_map: &mut HashMap<String, String>)
match command
Command::Add(name, dept) =>
employee_map.insert(name, dept);
()
,
Command::List =>
for (name, dept) in employee_map
println!("Name: Dept: ", name, dept);
;
,
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employee: HashMap<String, String> = HashMap::new();
let command = command_filter(String::from("Add John to Eng"));
process_command(command, &mut employee);
let command = command_filter(String::from("Add Devon to Bio"));
process_command(command, &mut employee);
let command = command_filter(String::from("List"));
process_command(command, &mut employee);
As with most devs starting to learn Rust I am finding difficulty with borrowing. I do not come from a C/C++ background and so I'm also finding pointers versus actual object a little tricky too.
Some specific thoughts:
- I am suspicious that match statements inside match statements is not good practice.
- The
Command::Addbranch inprocess_commandfeels a little hacky with the()return. Edit: After thinking more about this problem, I think if I wrapped the branch responses inOk()then I could return aResultfrom that function.
Do you have any feedback?
beginner rust
edited Jun 2 at 8:09
asked May 2 at 21:06
Mungo Dewar
686
686
To get better feedback I suggest you add some comments of your own. What do you think might be lacking? Are there things you're not quite sure of? Which part did you find the hardest to implement?
â Rene Saarsoo
May 3 at 12:30
Hi, thanks. I'll add some of my thoughts!
â Mungo Dewar
May 3 at 19:39
add a comment |Â
To get better feedback I suggest you add some comments of your own. What do you think might be lacking? Are there things you're not quite sure of? Which part did you find the hardest to implement?
â Rene Saarsoo
May 3 at 12:30
Hi, thanks. I'll add some of my thoughts!
â Mungo Dewar
May 3 at 19:39
To get better feedback I suggest you add some comments of your own. What do you think might be lacking? Are there things you're not quite sure of? Which part did you find the hardest to implement?
â Rene Saarsoo
May 3 at 12:30
To get better feedback I suggest you add some comments of your own. What do you think might be lacking? Are there things you're not quite sure of? Which part did you find the hardest to implement?
â Rene Saarsoo
May 3 at 12:30
Hi, thanks. I'll add some of my thoughts!
â Mungo Dewar
May 3 at 19:39
Hi, thanks. I'll add some of my thoughts!
â Mungo Dewar
May 3 at 19:39
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
2
down vote
accepted
General
- It feels strange to have functions just hanging around; why not create some methods on types? I moved
command_filtertoFromand movedprocess_commandtoCommand::process. The latter would probably make more sense as aprocessmethod on aEmployeeMaptype, though.
Command
It's unclear what the two values are for
Command::Add- use named fields instead.Could contain
&strs since they live shorter than the string they parsed.
Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice?
You would need to use a
String(which is an allocation, yes) if theCommandneeds to contain the string value longer than the input string value is available. In this case, all of your input strings are hard-coded literals so they are available "forever". In a different case, you might have read some user input into anotherString. So long as theCommandwent out of scope before thatStringmoved, it would be good.
Lifetimes are hard
Yes... and no. The thing is that languages like C or C++ have the exact same problems, but the languages don't stop you from doing The Wrong Thing. Languages with a garbage collector don't allow you to achieve the same level of efficiency. Rust's lifetimes allow you to be efficient while preventing memory unsafety.
command_filter
Could take a
&stras an argument since it doesn't make use of the allocation of theString.This advances the
split_whitespaceiterator before checking the result of a previousnextcall:match (command_string.next(), command_string.last())This isn't guaranteed to do what you want:
calling
next()again may or may not eventually start returningSome(Item)again at some point.You should use
Iterator::fuse.Could choose to flatten out the
matchto one level, but this would require advancing the iterator when you don't need to. Nested matches aren't inherently bad.
process_command
- You don't need to say
()as the return expression, that's the default value for a statement (lines ending in;).
main
There's no reason to specify the type of
employees, the compiler can infer it.employeesis a collection, so it should be a plural noun.
use std::collections::HashMap;
#[derive(Debug)]
enum Command<'a>
Add name: &'a str, dept: &'a str ,
List,
Unknown,
impl<'a> From<&'a str> for Command<'a>
fn from(input: &'a str) -> Self
let mut command_string = input.split_whitespace().fuse();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add name, dept ,
_ => Command::Unknown,
,
Some("List") => Command::List,
_ => Command::Unknown,
impl<'a> Command<'a>
fn process(self, employees: &mut HashMap<String, String>)
match self
Command::Add name, dept =>
employees.insert(name.to_owned(), dept.to_owned());
Command::List =>
for (name, dept) in employees
println!("Name: Dept: ", name, dept);
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employees = HashMap::new();
let command = Command::from("Add John to Eng");
command.process(&mut employees);
let command = Command::from("Add Devon to Bio");
command.process(&mut employees);
let command = Command::from("List");
command.process(&mut employees);
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked thenextvalue - how doesfuseprotect me? process_command: Of course, whoops.
â Mungo Dewar
May 8 at 13:35
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
1
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
1
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
add a comment |Â
up vote
3
down vote
Hide hashmap implementation
The code can be improved by removing the requirement for the user to operate on a hashmap or to even know that a hashmap is used.
How? This should be hidden from the user by an "interface".
Why?
This is a good coding practice as it would allow the underlying structure/code to change (for example, the hashmap could be swapped for a db) but there would be no API changes to the interface.
I have implemented this change and the changes suggested by @Shepmaster here: github
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
General
- It feels strange to have functions just hanging around; why not create some methods on types? I moved
command_filtertoFromand movedprocess_commandtoCommand::process. The latter would probably make more sense as aprocessmethod on aEmployeeMaptype, though.
Command
It's unclear what the two values are for
Command::Add- use named fields instead.Could contain
&strs since they live shorter than the string they parsed.
Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice?
You would need to use a
String(which is an allocation, yes) if theCommandneeds to contain the string value longer than the input string value is available. In this case, all of your input strings are hard-coded literals so they are available "forever". In a different case, you might have read some user input into anotherString. So long as theCommandwent out of scope before thatStringmoved, it would be good.
Lifetimes are hard
Yes... and no. The thing is that languages like C or C++ have the exact same problems, but the languages don't stop you from doing The Wrong Thing. Languages with a garbage collector don't allow you to achieve the same level of efficiency. Rust's lifetimes allow you to be efficient while preventing memory unsafety.
command_filter
Could take a
&stras an argument since it doesn't make use of the allocation of theString.This advances the
split_whitespaceiterator before checking the result of a previousnextcall:match (command_string.next(), command_string.last())This isn't guaranteed to do what you want:
calling
next()again may or may not eventually start returningSome(Item)again at some point.You should use
Iterator::fuse.Could choose to flatten out the
matchto one level, but this would require advancing the iterator when you don't need to. Nested matches aren't inherently bad.
process_command
- You don't need to say
()as the return expression, that's the default value for a statement (lines ending in;).
main
There's no reason to specify the type of
employees, the compiler can infer it.employeesis a collection, so it should be a plural noun.
use std::collections::HashMap;
#[derive(Debug)]
enum Command<'a>
Add name: &'a str, dept: &'a str ,
List,
Unknown,
impl<'a> From<&'a str> for Command<'a>
fn from(input: &'a str) -> Self
let mut command_string = input.split_whitespace().fuse();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add name, dept ,
_ => Command::Unknown,
,
Some("List") => Command::List,
_ => Command::Unknown,
impl<'a> Command<'a>
fn process(self, employees: &mut HashMap<String, String>)
match self
Command::Add name, dept =>
employees.insert(name.to_owned(), dept.to_owned());
Command::List =>
for (name, dept) in employees
println!("Name: Dept: ", name, dept);
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employees = HashMap::new();
let command = Command::from("Add John to Eng");
command.process(&mut employees);
let command = Command::from("Add Devon to Bio");
command.process(&mut employees);
let command = Command::from("List");
command.process(&mut employees);
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked thenextvalue - how doesfuseprotect me? process_command: Of course, whoops.
â Mungo Dewar
May 8 at 13:35
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
1
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
1
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
add a comment |Â
up vote
2
down vote
accepted
General
- It feels strange to have functions just hanging around; why not create some methods on types? I moved
command_filtertoFromand movedprocess_commandtoCommand::process. The latter would probably make more sense as aprocessmethod on aEmployeeMaptype, though.
Command
It's unclear what the two values are for
Command::Add- use named fields instead.Could contain
&strs since they live shorter than the string they parsed.
Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice?
You would need to use a
String(which is an allocation, yes) if theCommandneeds to contain the string value longer than the input string value is available. In this case, all of your input strings are hard-coded literals so they are available "forever". In a different case, you might have read some user input into anotherString. So long as theCommandwent out of scope before thatStringmoved, it would be good.
Lifetimes are hard
Yes... and no. The thing is that languages like C or C++ have the exact same problems, but the languages don't stop you from doing The Wrong Thing. Languages with a garbage collector don't allow you to achieve the same level of efficiency. Rust's lifetimes allow you to be efficient while preventing memory unsafety.
command_filter
Could take a
&stras an argument since it doesn't make use of the allocation of theString.This advances the
split_whitespaceiterator before checking the result of a previousnextcall:match (command_string.next(), command_string.last())This isn't guaranteed to do what you want:
calling
next()again may or may not eventually start returningSome(Item)again at some point.You should use
Iterator::fuse.Could choose to flatten out the
matchto one level, but this would require advancing the iterator when you don't need to. Nested matches aren't inherently bad.
process_command
- You don't need to say
()as the return expression, that's the default value for a statement (lines ending in;).
main
There's no reason to specify the type of
employees, the compiler can infer it.employeesis a collection, so it should be a plural noun.
use std::collections::HashMap;
#[derive(Debug)]
enum Command<'a>
Add name: &'a str, dept: &'a str ,
List,
Unknown,
impl<'a> From<&'a str> for Command<'a>
fn from(input: &'a str) -> Self
let mut command_string = input.split_whitespace().fuse();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add name, dept ,
_ => Command::Unknown,
,
Some("List") => Command::List,
_ => Command::Unknown,
impl<'a> Command<'a>
fn process(self, employees: &mut HashMap<String, String>)
match self
Command::Add name, dept =>
employees.insert(name.to_owned(), dept.to_owned());
Command::List =>
for (name, dept) in employees
println!("Name: Dept: ", name, dept);
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employees = HashMap::new();
let command = Command::from("Add John to Eng");
command.process(&mut employees);
let command = Command::from("Add Devon to Bio");
command.process(&mut employees);
let command = Command::from("List");
command.process(&mut employees);
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked thenextvalue - how doesfuseprotect me? process_command: Of course, whoops.
â Mungo Dewar
May 8 at 13:35
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
1
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
1
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
General
- It feels strange to have functions just hanging around; why not create some methods on types? I moved
command_filtertoFromand movedprocess_commandtoCommand::process. The latter would probably make more sense as aprocessmethod on aEmployeeMaptype, though.
Command
It's unclear what the two values are for
Command::Add- use named fields instead.Could contain
&strs since they live shorter than the string they parsed.
Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice?
You would need to use a
String(which is an allocation, yes) if theCommandneeds to contain the string value longer than the input string value is available. In this case, all of your input strings are hard-coded literals so they are available "forever". In a different case, you might have read some user input into anotherString. So long as theCommandwent out of scope before thatStringmoved, it would be good.
Lifetimes are hard
Yes... and no. The thing is that languages like C or C++ have the exact same problems, but the languages don't stop you from doing The Wrong Thing. Languages with a garbage collector don't allow you to achieve the same level of efficiency. Rust's lifetimes allow you to be efficient while preventing memory unsafety.
command_filter
Could take a
&stras an argument since it doesn't make use of the allocation of theString.This advances the
split_whitespaceiterator before checking the result of a previousnextcall:match (command_string.next(), command_string.last())This isn't guaranteed to do what you want:
calling
next()again may or may not eventually start returningSome(Item)again at some point.You should use
Iterator::fuse.Could choose to flatten out the
matchto one level, but this would require advancing the iterator when you don't need to. Nested matches aren't inherently bad.
process_command
- You don't need to say
()as the return expression, that's the default value for a statement (lines ending in;).
main
There's no reason to specify the type of
employees, the compiler can infer it.employeesis a collection, so it should be a plural noun.
use std::collections::HashMap;
#[derive(Debug)]
enum Command<'a>
Add name: &'a str, dept: &'a str ,
List,
Unknown,
impl<'a> From<&'a str> for Command<'a>
fn from(input: &'a str) -> Self
let mut command_string = input.split_whitespace().fuse();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add name, dept ,
_ => Command::Unknown,
,
Some("List") => Command::List,
_ => Command::Unknown,
impl<'a> Command<'a>
fn process(self, employees: &mut HashMap<String, String>)
match self
Command::Add name, dept =>
employees.insert(name.to_owned(), dept.to_owned());
Command::List =>
for (name, dept) in employees
println!("Name: Dept: ", name, dept);
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employees = HashMap::new();
let command = Command::from("Add John to Eng");
command.process(&mut employees);
let command = Command::from("Add Devon to Bio");
command.process(&mut employees);
let command = Command::from("List");
command.process(&mut employees);
General
- It feels strange to have functions just hanging around; why not create some methods on types? I moved
command_filtertoFromand movedprocess_commandtoCommand::process. The latter would probably make more sense as aprocessmethod on aEmployeeMaptype, though.
Command
It's unclear what the two values are for
Command::Add- use named fields instead.Could contain
&strs since they live shorter than the string they parsed.
Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice?
You would need to use a
String(which is an allocation, yes) if theCommandneeds to contain the string value longer than the input string value is available. In this case, all of your input strings are hard-coded literals so they are available "forever". In a different case, you might have read some user input into anotherString. So long as theCommandwent out of scope before thatStringmoved, it would be good.
Lifetimes are hard
Yes... and no. The thing is that languages like C or C++ have the exact same problems, but the languages don't stop you from doing The Wrong Thing. Languages with a garbage collector don't allow you to achieve the same level of efficiency. Rust's lifetimes allow you to be efficient while preventing memory unsafety.
command_filter
Could take a
&stras an argument since it doesn't make use of the allocation of theString.This advances the
split_whitespaceiterator before checking the result of a previousnextcall:match (command_string.next(), command_string.last())This isn't guaranteed to do what you want:
calling
next()again may or may not eventually start returningSome(Item)again at some point.You should use
Iterator::fuse.Could choose to flatten out the
matchto one level, but this would require advancing the iterator when you don't need to. Nested matches aren't inherently bad.
process_command
- You don't need to say
()as the return expression, that's the default value for a statement (lines ending in;).
main
There's no reason to specify the type of
employees, the compiler can infer it.employeesis a collection, so it should be a plural noun.
use std::collections::HashMap;
#[derive(Debug)]
enum Command<'a>
Add name: &'a str, dept: &'a str ,
List,
Unknown,
impl<'a> From<&'a str> for Command<'a>
fn from(input: &'a str) -> Self
let mut command_string = input.split_whitespace().fuse();
match command_string.next()
Some("Add") =>
match (command_string.next(), command_string.last())
(Some(name), Some(dept)) => Command::Add name, dept ,
_ => Command::Unknown,
,
Some("List") => Command::List,
_ => Command::Unknown,
impl<'a> Command<'a>
fn process(self, employees: &mut HashMap<String, String>)
match self
Command::Add name, dept =>
employees.insert(name.to_owned(), dept.to_owned());
Command::List =>
for (name, dept) in employees
println!("Name: Dept: ", name, dept);
Command::Unknown => println!("Unknown command!"),
;
fn main()
let mut employees = HashMap::new();
let command = Command::from("Add John to Eng");
command.process(&mut employees);
let command = Command::from("Add Devon to Bio");
command.process(&mut employees);
let command = Command::from("List");
command.process(&mut employees);
edited May 8 at 21:44
answered May 8 at 0:27
Shepmaster
6,5881018
6,5881018
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked thenextvalue - how doesfuseprotect me? process_command: Of course, whoops.
â Mungo Dewar
May 8 at 13:35
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
1
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
1
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
add a comment |Â
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked thenextvalue - how doesfuseprotect me? process_command: Of course, whoops.
â Mungo Dewar
May 8 at 13:35
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
1
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
1
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked the
next value - how does fuse protect me? process_command: Of course, whoops.â Mungo Dewar
May 8 at 13:35
Wow, thank you so much! General: I forgot that you can implement methods on enums. That makes a ton of sense. Command: 1. Nice! Didn't know that could be done/was valid syntax. 2. Ah I see, and I would only need a new string (and hence allocation?) if the lifetime of the string was shorter than the slice? Lifetimes are hard. command_filter: 1. Does this mean memory allocation? 2. I don't understand, I thought I created the Iterator and immediately checked the
next value - how does fuse protect me? process_command: Of course, whoops.â Mungo Dewar
May 8 at 13:35
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
@MungoDewar updated to address some feedback.
â Shepmaster
May 8 at 21:44
1
1
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
Wanted to share updated code. I've included the comments listed here and have also hidden the Hashmap implementation behind the App struct. github.com/DewarM/rust_manage_employee
â Mungo Dewar
May 30 at 21:37
1
1
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
@MungoDewar I believe you are also encouraged to provide your own answer here, expanding on that comment and explaining why you made the changes you did.
â Shepmaster
May 31 at 2:30
add a comment |Â
up vote
3
down vote
Hide hashmap implementation
The code can be improved by removing the requirement for the user to operate on a hashmap or to even know that a hashmap is used.
How? This should be hidden from the user by an "interface".
Why?
This is a good coding practice as it would allow the underlying structure/code to change (for example, the hashmap could be swapped for a db) but there would be no API changes to the interface.
I have implemented this change and the changes suggested by @Shepmaster here: github
add a comment |Â
up vote
3
down vote
Hide hashmap implementation
The code can be improved by removing the requirement for the user to operate on a hashmap or to even know that a hashmap is used.
How? This should be hidden from the user by an "interface".
Why?
This is a good coding practice as it would allow the underlying structure/code to change (for example, the hashmap could be swapped for a db) but there would be no API changes to the interface.
I have implemented this change and the changes suggested by @Shepmaster here: github
add a comment |Â
up vote
3
down vote
up vote
3
down vote
Hide hashmap implementation
The code can be improved by removing the requirement for the user to operate on a hashmap or to even know that a hashmap is used.
How? This should be hidden from the user by an "interface".
Why?
This is a good coding practice as it would allow the underlying structure/code to change (for example, the hashmap could be swapped for a db) but there would be no API changes to the interface.
I have implemented this change and the changes suggested by @Shepmaster here: github
Hide hashmap implementation
The code can be improved by removing the requirement for the user to operate on a hashmap or to even know that a hashmap is used.
How? This should be hidden from the user by an "interface".
Why?
This is a good coding practice as it would allow the underlying structure/code to change (for example, the hashmap could be swapped for a db) but there would be no API changes to the interface.
I have implemented this change and the changes suggested by @Shepmaster here: github
edited Jun 3 at 18:04
Sam Onela
5,77461543
5,77461543
answered Jun 2 at 8:18
Mungo Dewar
686
686
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193501%2fthe-rust-book-hashmap-challenge%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
To get better feedback I suggest you add some comments of your own. What do you think might be lacking? Are there things you're not quite sure of? Which part did you find the hardest to implement?
â Rene Saarsoo
May 3 at 12:30
Hi, thanks. I'll add some of my thoughts!
â Mungo Dewar
May 3 at 19:39