I started using rust a few days ago, and I want some feedback on my first mini project. I am trying to improve the way I structure my project, best practises while making structs and using macros and if there is anything I need to improve on. Thanks in advance >-< https://github.com/steveyxz/console-wordle-rust
#Can I have some feedback
50 messages ยท Page 1 of 1 (latest)
I like that you're doing cargo fmt everytime you run, your editor can probably do that automatically.
are you on vscode?
nah im on lapcec
lapce
i use vscode for js tho
but i like lapce better for rust
because it has similar functionality
but it feels faster
idk why
you could probably search for "rustfmt on save" for that then
yea i have that on
i just didnt remove it from the bat after i changed the setting ๐
it only takes like a split second tho so it doesnt really affect it much
the code is the main part i need review
the
#[macro_use]
extern crate json;
is sort of outdated, you still see it sometimes in docs and tutorials, but you should use use json::{object} and so on instead
In Cargo.toml, I would recommend against using "*" when specifying the version of dependencies. While in this case, the json crate probably won't significantly change its API, it could at some point, which may suddenly make your program fail to compile next time cargo refreshes the dependencies.
On the point of json, I'd also suggest looking into the serde and serde_json crates. You can use it to deserialize JSON into concrete types (or serialize types into JSON).
This is a few different concepts wrapped together, but you'd like, you can clean up the max_attempt input loop (https://github.com/steveyxz/console-wordle-rust/blob/main/src/main.rs#L39-L74) to something like this:
let max_attempts: usize = loop {
print_with_style(
"How many attempts would you like per round? (number 3-20)",
&main_text_style,
&terminal,
);
let input = terminal.read_line().expect("failed to read input"); // If you're not going to handle unwraps, then at least use .expect() instead. Additionally, read_line()'s Ok type, is always String, so you don't have to declare the type manually.
if input.is_empty() { // if the input is empty, break with default; no need to try to parse it.
break 6;
}
match input.parse() { // compiler can infer the return type of parse()
Ok(result) if result >= 3 && result <= 20 => break result, // this uses a "match guard"
Ok(_) => print_with_style(
"Make sure to enter a number between 3-20",
&main_error_style,
&terminal,
),
Err(_) => print_with_style(
"Make sure to enter a valid number!",
&main_error_style,
&terminal,
),
}
};
```Similar to how you can `return` from a function with a value, you can also `break` out of a loop, and return a value with it.
https://github.com/steveyxz/console-wordle-rust/blob/main/src/main.rs#L82-L102 You can use a match terminal.read_line().expect("failed to read input") {} , instead of using the if-else-if blocks.
https://github.com/steveyxz/console-wordle-rust/blob/main/src/main.rs#L115 When you need to format a string, you can use the format! macro. So instead of this line, you could write:
&format!("Ok {name}! Your wordle will now begin!")
Rest of main.rs look good I think, besides some more occurrences of stuff I mentioned.
Small thing, but in https://github.com/steveyxz/console-wordle-rust/blob/main/src/lib.rs#L75, there's a few things you could do here. At the very least, if you ever want a reference to String, you should use &str, not &String. Alternatively, you could just use String, and the caller would possibly need to clone(). Or, you can use generics like this:
pub fn guess<S: ToString>(&mut self, word: S) -> bool {
self.guessed_words.push(word.to_string());
// ...
}
```This allows the caller to provide any type that implements the ToString trait. Both `&str` and `String` implement it.
Similarly, in cases where you need a `&str` you can use
```Rust
fn foo<S: AsRef<str>>(text: S) {
let text = text.as_ref();
// do stuff
}
In all the functions in lib.rs, that return a value, note that you don't have to explicitly write return every time. And conventionally, people don't unless they have to.
You're already doing it with is_over(), but I can show another example using guess(). Plus, in this case, because you're returning true/false depending on a function which already returns true/false, you can also get rid of the if statement entirely.
pub fn guess(&mut self, word: &String) -> bool {
self.guessed_words.push(word.to_string());
self.current_guesses += 1;
self.is_over()
}
```(Note there is no semicolon after `is_over()`; that's intentional.)
The rest of the code looks good.
Nice job on your first project in Rust! There wasn't really anything wrong with your code, and the use of cargo fmt is great (like limepod mentioned). You don't have to make any of the changes I said above; they're just there for you to consider.
Thanks for the dependencies trick I was confused about the use of "*" in the version. For the json library I just used the first one in crates.io when i searched json, but I will keep those libraries in mind for future projects. The use of loop and using break {value} is really foreign to me (coming from a java background), and your example was really helpful in understanding both if use of loop inline, and also further extensions of match (i didn't know you could have multiple cases of the same Ok)
This was really helpful when I wrote that code I wasn't really sure how to fix the mismatched types error so I just tried making them all into Strings, rather than str. format!() really helps
I didn't know this too, I'll make sure to keep it in mind. I didn't think it was very efficient to use .clone() for every time you run that, so I used a reference because that's how I usually fix use of moved variable errors ๐
I was aware of this format but I was a bit confused because from a java standpoint just a statement feels illegal, so I just kept using return. I will keep this in mind for future projects
Thanks for the feedback ๐
It really helped
No problem, glad to help.
To be honest, in nearly all cases, you're going to end up cloning the string anyways. Only case where that wouldn't happen, of the provided solutions, was if the argument took a String, and the caller didn't need to reuse the string after calling the function. For &str and String types, to_owned and to_string will implicitly clone the underlying data. So all three solutions are essentially the same performance-wise.
I see
Another question, if ToString derivable
*is
Like with #[derive(ToString)]
No, and you wouldn't typically implement ToString directly anyways. Refer to the docs for details: https://doc.rust-lang.org/stable/std/string/trait.ToString.html.
(and no, you cannot derive(Display) either) :P
I see, implementing Display is very similar to implementing debug right
actually its the same
Debug can be derived, so not really?
as in the contents of the implementation are similar?
like write!(xxx) or something
Sorta yeah
thanks
do you have any recommendations for a graphics library
i want to make a basic game
like pong
preferably i would want a library which isn't just a bunch of binding for opengl
or vulkan
Hmm I'd suggest asking in #games-and-graphics for that. I don't typically write games.
ok thanks