June 2025
I’m a Rust noobie. Learned because some clients wanted to run Rust in React-Native. Because of that I’ve been learning on the go and relying on co-pilot to teach me the basic concepts. But LLMs are regurgitating machines are not the best at giving the idiomatic code. Here are some of the tips I’ve learned. With caveats of course, assume you can use std
, etc.
Don’t use lazy_static
or once_cell
crates, their functionality has been incorporated into the standard lib (std
) and one can now just use OnceLock
and LazyLock
to initialize global variables.
// ❌ Don't
lazy_static! {
// your global variables
}
// ✅ Do
static MY_GLOBAL_STRING: LazyLock<RwLock<String>> =
LazyLock::new(|| RwLock::new("Hello World!".into()));
Generally speaking RwLock
is what you want instead of Mutex
. It allows for multiple readers without fully locking your process. That being said, if you will read and write within the same function it’s very important to free any reader lock!
let my_read_var = MY_VAR.read().unwrap()
// If you don't drop
drop(my_read_var)
// This writer will lock
let mut my_write_var = MY_VAR.write().unwrap();
cfg
s to avoid async traits, send+sync usageIf you are exposing a C-API and returning std::ffi::Cstring
, strings must be returned to Rust to be safely de-allocated.
#[no_mangle]
unsafe extern "C" fn get_a_string() -> *mut c_char {
let data = CString::new("Hello World!".into()).unwrap();
data.into_raw() as *mut c_char
}
// The pointer must be later returned to Rust for safe de-allocation
#[no_mangle]
unsafe extern "C" fn free_string(ptr: *mut c_char) {
if ptr.is_null() {
return;
}
let _ = CString::from_raw(ptr);
// Automatically dropped at the end of function
}
Result
and Option
. They allow for very idiomatic and terse Rust code. Enforce their usage.nativetls
vs rustls
crates. If you are targeting multi-platform go with rustls
if possible.Ring
is being deprecated/on-hold/abandoned, a lot of libraries are migrating to aws-lc-rs
, so should you.
#[cfg(test)]
or #[cfg(debug)]
but this can have issues down the road with hidden errors that are not detected while developing. I’ve found using a if cfg!(test)
is sometimes better as all the branches of your code are compiled and avoid a lot of dead compilation zones that only float up that might be hiding deeper compilation issues.?
seems to be the recommended way of doing things, but one looses the exact line where the error was thrown? I’m not sure if I’m doing things wrong. In any case, it’s better to use that with very specific error types.assert2
crate is awesome and it will make your tests easier to debug by outputting the values with colors, instead of just opaque errors.