2022-12-29 (last edit: 2022-12-29)
Always ask yourself twice if you really need to unwrap. In most cases, you don't have to. Use pattern matching instead, as it provides a static guarantee that the value is present.
Pattern matching prevents you from writing code like this:
fn main() {
let x: Option<i32> = some_function();
if x.is_some() {
println!("x is {}", x.unwrap());
}
// Let's say this line was added later and/or you forgot to put it in the if statement.
do_something(x.unwrap()); // this will blow up if x == None!
}
Instead, you can write:
fn main() {
let x: Option<i32> = some_function();
if let Some(x) = x {
println!("x is {}", x);
do_something(x);
}
}
In methods that return Result
or Option
, you can use the question mark operator to return early if the value is None
or Err
.
See: https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html
You can use the log crate to log messages. It's better than println!
because it
can be easily turned off. It also allows you to use different severity levels (e.g. info
, warn
, error
) and only
log messages above a certain level.
See https://doc.rust-lang.org/book/ch04-03-slices.html#string-slices-as-parameters
In general, if you want to pass a reference to a string, use &str
instead of &String
.
You can use cargo upgrades to check for outdated dependencies.
You can have multiple binaries in a single cargo project. Simply place them in the src/bin
directory.
You can run them with cargo run --bin <name>
. Alternatively, you can setup a
workspace.
This should have become a habit by now. You can disable clippy warnings for a single item with #[allow(clippy::...)]
,
but in most cases you shouldn't do that.
See https://doc.rust-lang.org/reference/tokens.html#raw-string-literals
Short: https://kerkour.com/rust-error-handling
Long: https://www.lpalmieri.com/posts/error-handling-rust/
If you have a mutex, you can use lock()
to get a guard that will unlock the mutex when it goes out of scope.
But don't pass the contents of the guard to functions that can block (unless the mutex must be locked for
the entire duration of the function).
Instead of:
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use std::time::Instant;
fn handle_function(counter: &mut i32) {
thread::sleep(Duration::from_secs(1));
*counter += 1;
thread::sleep(Duration::from_secs(1));
}
fn main() {
let counter = Mutex::new(1);
thread::scope(|s| {
for i in 0..10 {
let counter = &counter;
s.spawn(move || {
println!("Thread {i} started");
let now = Instant::now();
let mut counter = counter.lock().unwrap();
handle_function(&mut counter); // lock is held for 2 seconds
println!("Thread {i} finished after {}s", now.elapsed().as_secs());
});
}
})
}
You should do this:
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use std::time::Instant;
fn handle_function(counter: &Mutex<i32>) { // <-- changed
thread::sleep(Duration::from_secs(1));
{
let mut counter = counter.lock().unwrap(); // <-- changed
*counter += 1;
// lock is held only for the duration of the block
// it is important to create a new scope here, otherwise the lock would be held for another second
}
thread::sleep(Duration::from_secs(1));
}
fn main() {
let counter = Mutex::new(1);
thread::scope(|s| {
for i in 0..10 {
let counter = &counter;
s.spawn(move || {
println!("Thread {i} started");
let now = Instant::now();
handle_function(counter); // <-- changed! we don't lock here
println!("Thread {i} finished after {}s", now.elapsed().as_secs());
});
}
})
}
Compare the output of the two programs. The first one will take 20 seconds to finish, while the second one will take 2 seconds.
First one:
Thread 1 started
Thread 0 started
Thread 2 started
Thread 3 started
Thread 4 started
Thread 5 started
Thread 6 started
Thread 7 started
Thread 8 started
Thread 9 started
Thread 1 finished after 2s
Thread 0 finished after 4s
Thread 2 finished after 6s
Thread 3 finished after 8s
Thread 4 finished after 10s
Thread 5 finished after 12s
Thread 6 finished after 14s
Thread 7 finished after 16s
Thread 8 finished after 18s
Thread 9 finished after 20s
Second one:
Thread 0 started
Thread 2 started
Thread 1 started
Thread 3 started
Thread 4 started
Thread 5 started
Thread 6 started
Thread 7 started
Thread 8 started
Thread 9 started
Thread 1 finished after 2s
Thread 2 finished after 2s
Thread 0 finished after 2s
Thread 3 finished after 2s
Thread 4 finished after 2s
Thread 5 finished after 2s
Thread 6 finished after 2s
Thread 7 finished after 2s
Thread 8 finished after 2s
Thread 9 finished after 2s