Hi there! I'm somewhat new to Rust, and I've created my first "real" (real as in not a <100 line script) project. It's only ~500 lines, but I'm hoping to get feedback on:
a) Does the code follow good idiomatic Rust guidelines?
b) Specifically relating to the multi-threading: did I use the Arc<Mutex<...>> correctly?
c) Is the code well written; are there parts that are unnecessarily complex, unnecessarily inefficient, or unnecessarily verbose?
d) Any other general feedback.
When handling file paths, use &Path instead of &str, and PathBuf instead of String.
Never use &Vec<T>. It's a type that demands that the vector can grow in size, but also & forbids the vector from growing β this type makes no sense. Every &Vec<T> can be cast to &[T]. Owned Vec<T> is fine
Don't use .unwrap(), especially in functions returning Result. Use ? instead. Use maybe.map_err(|e| io::Error::new(β¦))? if the error types aren't compatible.
Use AtomicUsize instead of Mutex<usize>. If you use scoped threads, you won't need Arc.
I ran clippy, and I implemented its suggestions. Thank you for the tip of using &[T] instead of &Vec<T>.
I'll have to look into AtomicUsize, but that sounds like a very neat solution instead of having to use a mutex. Thanks for that tip!
But I had some questions about these:
I'm already using &Path for many of the function signatures, but I kept the String in the Song struct instead of replacing it with PathBuf since I'm using Serde to serialize those structs. Does PathBuf work fine with Serde?
All of the places that I do use .unwrap() are places where I do want the program to panic and crash if there's an issue. Should I always avoid unwrapping? Doesn't that complicate error handling unnecessarily in some cases?
Overall, thanks again for taking the time to suggest these things!
serde itself handles PathBuf fine (assuming you are not serializing for cross-platform uses). I'm wrong. serde treats Path as a string. RIP. You can use OsString, which works the way I expect PathBuf to.
serde_json has some interoperability problems with JSON, though.
mutex.lock().unwrap() is perfectly justified, since lock poisoning is just annoying. I use parking_lot just to get that out of the way.
I wouldn't panic on channel.send(). Just exit the thread quietly. The channels usually get disconnected during program's shutdown, and shutdown by crashing is inelegant.
Panicking when audio doesn't initialize also seems excessive to me. Although this is a fatal error for the program, it is a run-time condition that can happen in correctly-written program. I would use panics only for bugs that require fixing the program itself, not issues with its runtime environment that are outside of your control.
This is especially bad if you unwrap on Option, because user will just get error that None happened, not that their songs went missing or audio drivers are broken.
There are crates like anyhow and quick-error that help with the error handling boilerplate.
Rust has match, if let, let else, and dozens of helper functions on options and results to avoid using .unwrap() in normal code. With just .unwrap() it's hard to tell when that's just lazy coding, or an intentional panic. .expect(β¦) is clearer. For bug catching, assert! shows intention even better.