style: crank clippy to pedantic+nursery and fix all lints

Enable the full clippy all + pedantic + nursery groups as workspace warnings,
with a small documented allow-list for the pedantic/nursery lints this project
intentionally does not follow (rustdoc-style requirements, deliberate numeric
casts in the protocol/UI layers, and a few noisy nursery lints). Also enable
the rust unused_lifetimes and unsafe_op_in_unsafe_fn lints.

Fixed every resulting warning (527 -> 0) mechanically per clippy's suggestions:
inline format args, const fns, item ordering, let-else, redundant clones,
map_or, cast_lossless, Eq derives, associated-fn conversions for methods that
never used self, and removing unnecessary Result wrappers whose call sites could
never propagate an error. No runtime behavior changed; cargo fmt applied.

cargo clippy --workspace --all-targets: 0 warnings. All tests pass.
This commit is contained in:
michel
2026-07-19 00:09:17 +02:00
parent b149a8ecf2
commit e72c8a5c07
37 changed files with 482 additions and 433 deletions
+33 -57
View File
@@ -32,12 +32,10 @@ pub fn init() {
unsafe {
LOG_LEVEL = match level.as_str() {
"ERROR" => LogLevel::Error,
"WARN" => LogLevel::Warn,
"INFO" => LogLevel::Info,
"DEBUG" => LogLevel::Debug,
"DEBUG" | "VERBOSE" => LogLevel::Debug, // Map VERBOSE to DEBUG
"TRACE" => LogLevel::Trace,
"VERBOSE" => LogLevel::Debug, // Map VERBOSE to DEBUG
_ => LogLevel::Warn, // Default to WARN
_ => LogLevel::Warn, // "WARN" or default
};
}
@@ -54,10 +52,7 @@ pub fn init() {
}
}
Err(e) => {
eprintln!(
"Failed to open log file '{}': {}",
log_file_path, e
);
eprintln!("Failed to open log file '{log_file_path}': {e}");
}
}
}
@@ -74,7 +69,7 @@ enum LogSink {
Stderr,
}
fn choose_sink(buffering: bool, has_file: bool) -> LogSink {
const fn choose_sink(buffering: bool, has_file: bool) -> LogSink {
match (buffering, has_file) {
// A configured file always takes the line directly and bypasses
// buffering, so the flush step never re-writes it.
@@ -86,7 +81,7 @@ fn choose_sink(buffering: bool, has_file: bool) -> LogSink {
}
fn has_log_file() -> bool {
LOG_FILE.lock().map(|f| f.is_some()).unwrap_or(false)
LOG_FILE.lock().is_ok_and(|f| f.is_some())
}
pub fn log(level: LogLevel, message: &str) {
@@ -161,29 +156,11 @@ pub fn log(level: LogLevel, message: &str) {
};
let formatted_message = format!(
"[{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}] [{}] {}",
year,
month,
day,
hours,
minutes,
seconds,
subsec_millis,
level_str,
message
"[{year:04}-{month:02}-{day:02} {hours:02}:{minutes:02}:{seconds:02}.{subsec_millis:03}] [{level_str}] {message}"
);
let formatted_message_plain = format!(
"[{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}] [{}] {}",
year,
month,
day,
hours,
minutes,
seconds,
subsec_millis,
level_str_plain,
message
"[{year:04}-{month:02}-{day:02} {hours:02}:{minutes:02}:{seconds:02}.{subsec_millis:03}] [{level_str_plain}] {message}"
);
match choose_sink(BUFFERING.load(Ordering::Relaxed), has_log_file())
@@ -192,18 +169,17 @@ pub fn log(level: LogLevel, message: &str) {
if let Ok(mut log_file) = LOG_FILE.lock()
&& let Some(file) = log_file.as_mut()
{
let _ =
writeln!(file, "{}", formatted_message_plain);
let _ = writeln!(file, "{formatted_message_plain}");
let _ = file.flush();
}
}
LogSink::Buffer => {
if let Ok(mut buffer) = BUFFER.lock() {
buffer.push(formatted_message.clone());
buffer.push(formatted_message);
}
}
LogSink::Stderr => {
eprintln!("{}", formatted_message);
eprintln!("{formatted_message}");
}
}
}
@@ -228,18 +204,18 @@ pub fn flush_buffered_logs() {
for message in buffer.iter() {
// Strip ANSI codes for file output
let plain_message = strip_ansi_codes(message);
let _ = writeln!(file, "{}", plain_message);
let _ = writeln!(file, "{plain_message}");
}
let _ = file.flush();
} else {
for message in buffer.iter() {
eprintln!("{}", message);
eprintln!("{message}");
}
}
} else {
// If lock fails, fall back to stderr
for message in buffer.iter() {
eprintln!("{}", message);
eprintln!("{message}");
}
}
buffer.clear();
@@ -270,26 +246,6 @@ fn strip_ansi_codes(s: &str) -> String {
result
}
#[cfg(test)]
mod tests {
use super::{LogSink, choose_sink};
#[test]
fn a_configured_file_bypasses_buffering_so_lines_are_not_duplicated() {
// With a file configured, the line goes straight to the file whether or
// not buffering is on, so it is never also queued for a flush that would
// write it a second time.
assert_eq!(choose_sink(true, true), LogSink::File);
assert_eq!(choose_sink(false, true), LogSink::File);
}
#[test]
fn without_a_file_buffering_routes_to_the_buffer_else_stderr() {
assert_eq!(choose_sink(true, false), LogSink::Buffer);
assert_eq!(choose_sink(false, false), LogSink::Stderr);
}
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
@@ -324,3 +280,23 @@ macro_rules! trace {
$crate::utils::logger::log($crate::utils::logger::LogLevel::Trace, &format!($($arg)*))
};
}
#[cfg(test)]
mod tests {
use super::{LogSink, choose_sink};
#[test]
fn a_configured_file_bypasses_buffering_so_lines_are_not_duplicated() {
// With a file configured, the line goes straight to the file whether or
// not buffering is on, so it is never also queued for a flush that would
// write it a second time.
assert_eq!(choose_sink(true, true), LogSink::File);
assert_eq!(choose_sink(false, true), LogSink::File);
}
#[test]
fn without_a_file_buffering_routes_to_the_buffer_else_stderr() {
assert_eq!(choose_sink(true, false), LogSink::Buffer);
assert_eq!(choose_sink(false, false), LogSink::Stderr);
}
}
+13 -12
View File
@@ -22,7 +22,7 @@ use std::convert::TryInto;
* K[i] = floor(2^32 * abs(sin(i+1)))
*/
fn table_construction_function(i: u32) -> u32 {
let x: f64 = i as f64;
let x: f64 = f64::from(i);
let sin_eval = x.sin().abs();
// note: 4294967296 == 2^32
(4294967296.0 * sin_eval) as u32
@@ -36,19 +36,19 @@ fn table_construction_function(i: u32) -> u32 {
* algorithm, and they are performed a number of times to end
* up with our final result.
*/
fn f(x: u32, y: u32, z: u32) -> u32 {
const fn f(x: u32, y: u32, z: u32) -> u32 {
x & y | !x & z
}
fn g(x: u32, y: u32, z: u32) -> u32 {
const fn g(x: u32, y: u32, z: u32) -> u32 {
x & z | y & !z
}
fn h(x: u32, y: u32, z: u32) -> u32 {
const fn h(x: u32, y: u32, z: u32) -> u32 {
x ^ y ^ z
}
fn i(x: u32, y: u32, z: u32) -> u32 {
const fn i(x: u32, y: u32, z: u32) -> u32 {
y ^ (x | !z)
}
@@ -67,7 +67,7 @@ fn vec_to_array<T, const N: usize>(v: Vec<T>) -> [T; N] {
* table[i] is an entry generated by our sine function, s is an unsigned
* integer, and a,b,c,d are our 32 bit registers we perform operations on.
*/
fn round_one_operations(
const fn round_one_operations(
mut a: u32,
mut b: u32,
mut c: u32,
@@ -115,7 +115,7 @@ fn round_one_operations(
* Round 2 is equivalent to round 1, except we swap out our f function for g as such:
* a = b + ((a + g(b,c,d) + x[k] + table[i]) <<< s);
*/
fn round_two_operations(
const fn round_two_operations(
mut a: u32,
mut b: u32,
mut c: u32,
@@ -161,7 +161,7 @@ fn round_two_operations(
* Round 3 is equivalent to round 1, except we swap out our f function for h as such:
* a = b + ((a + h(b,c,d) + x[k] + table[i]) <<< s);
*/
fn round_three_operations(
const fn round_three_operations(
mut a: u32,
mut b: u32,
mut c: u32,
@@ -207,7 +207,7 @@ fn round_three_operations(
* Round 4 is equivalent to round 1, except we swap out our f function for h as such:
* a = b + ((a + i(b,c,d) + x[k] + table[i]) <<< s);
*/
fn round_four_operations(
const fn round_four_operations(
mut a: u32,
mut b: u32,
mut c: u32,
@@ -287,7 +287,7 @@ fn compute_md5_digest(mut v: Vec<u8>) -> String {
let table = construct_value_table();
// let M[0 .. N-1] = words of resulting message, where N is multiple of 16
for chunk in v.chunks_exact_mut(64) {
for chunk in v.as_chunks_mut::<64>().0 {
let x = convert_u8_chunk_to_u32(chunk);
// set all values of a,b,c,d to aa,bb,cc, and dd respectively.
@@ -378,7 +378,7 @@ fn bit_padding(input: &str) -> Vec<u8> {
input_vector
}
fn split_u64_to_u8_array(s: u64) -> [u8; 8] {
const fn split_u64_to_u8_array(s: u64) -> [u8; 8] {
[
s as u8,
(s >> 8) as u8,
@@ -425,6 +425,7 @@ fn convert_str_to_vec(input: &str) -> Vec<u8> {
* during our bitwise operations to retrieve our message digest.
* (This is a simplification, but describes the core functionality)
*/
#[must_use]
pub fn md5(input: &str) -> String {
let input_vec = bit_padding(input);
compute_md5_digest(input_vec)
@@ -454,7 +455,7 @@ fn lowercase_alphabet_correct_hash() {
assert_eq!(
"c3fcd3d76192e4007dfb496cca67e13b",
md5("abcdefghijklmnopqrstuvwxyz")
)
);
}
#[test]
+1
View File
@@ -1,5 +1,6 @@
use std::path::PathBuf;
#[must_use]
pub fn expand_tilde(path: &str) -> PathBuf {
if let Some(stripped) = path.strip_prefix('~')
&& let Ok(home) = std::env::var("HOME")
+36 -35
View File
@@ -16,7 +16,8 @@ pub struct ThreadPool {
}
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
#[must_use]
pub fn new(size: usize) -> Self {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
@@ -27,7 +28,7 @@ impl ThreadPool {
workers.push(Worker::new(Arc::clone(&receiver)));
}
ThreadPool {
Self {
workers,
sender: Some(sender),
}
@@ -62,6 +63,39 @@ impl Drop for ThreadPool {
}
}
struct Worker {
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Self {
let thread = thread::spawn(move || {
loop {
let message = match receiver.lock_safe() {
Ok(rx) => rx.recv(),
Err(_) => break,
};
match message {
// Contain a panicking job so it kills only that job, not the
// worker: the pool has a fixed number of workers and a lost
// one is never replaced. The lock is already released here,
// so a panic cannot poison the shared receiver.
Ok(Message::NewJob(job)) => {
let _ = std::panic::catch_unwind(
std::panic::AssertUnwindSafe(job),
);
}
Ok(Message::Terminate) | Err(_) => break,
}
}
});
Self {
thread: Some(thread),
}
}
}
#[cfg(test)]
mod tests {
use super::ThreadPool;
@@ -88,36 +122,3 @@ mod tests {
);
}
}
struct Worker {
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let message = match receiver.lock_safe() {
Ok(rx) => rx.recv(),
Err(_) => break,
};
match message {
// Contain a panicking job so it kills only that job, not the
// worker: the pool has a fixed number of workers and a lost
// one is never replaced. The lock is already released here,
// so a panic cannot poison the shared receiver.
Ok(Message::NewJob(job)) => {
let _ = std::panic::catch_unwind(
std::panic::AssertUnwindSafe(job),
);
}
Ok(Message::Terminate) | Err(_) => break,
}
}
});
Worker {
thread: Some(thread),
}
}
}
+11 -11
View File
@@ -8,7 +8,7 @@ struct BitReader {
}
impl BitReader {
fn new(mem: Vec<u8>) -> Self {
const fn new(mem: Vec<u8>) -> Self {
Self {
mem,
pos: 0,
@@ -42,7 +42,7 @@ impl BitReader {
fn read_bits(&mut self, n: usize) -> std::result::Result<u32, String> {
let mut o = 0u32;
for i in 0..n {
o |= (self.read_bit()? as u32) << i;
o |= u32::from(self.read_bit()?) << i;
}
Ok(o)
}
@@ -51,7 +51,7 @@ impl BitReader {
// read bytes as an integer in little-endian
let mut o = 0u32;
for i in 0..n {
o |= (self.read_byte()? as u32) << (8 * i);
o |= u32::from(self.read_byte()?) << (8 * i);
}
Ok(o)
}
@@ -72,7 +72,7 @@ pub fn deflate(input: &[u8]) -> Result<Vec<u8>> {
return Err(SoulseekRs::CompressionError("invalid CINFO".to_string()));
}
let flg = r.read_byte()?;
if !((cmf as u32) * 256 + (flg as u32)).is_multiple_of(31) {
if !(u32::from(cmf) * 256 + u32::from(flg)).is_multiple_of(31) {
return Err(SoulseekRs::CompressionError(
"CMF+FLG checksum failed".to_string(),
));
@@ -119,12 +119,12 @@ fn inflate_block_no_compression(
#[derive(Clone)]
struct Node {
symbol: Option<u32>,
left: Option<Box<Node>>,
right: Option<Box<Node>>,
left: Option<Box<Self>>,
right: Option<Box<Self>>,
}
impl Node {
fn new() -> Self {
const fn new() -> Self {
Self {
symbol: None,
left: None,
@@ -138,7 +138,7 @@ struct HuffmanTree {
}
impl HuffmanTree {
fn new() -> Self {
const fn new() -> Self {
Self { root: Node::new() }
}
@@ -175,9 +175,9 @@ fn decode_symbol(
// at a missing child; error out instead of unwrap-panicking.
node = next
.as_ref()
.ok_or("Invalid Huffman code".to_string())?;
.ok_or_else(|| "Invalid Huffman code".to_string())?;
}
node.symbol.ok_or("No symbol found".to_string())
node.symbol.ok_or_else(|| "No symbol found".to_string())
}
const LENGTH_EXTRA_BITS: [usize; 29] = [
@@ -397,7 +397,7 @@ mod tests {
#[test]
fn test_extract_header_success() {
let _data = [120, 156]; // Valid zlib header
// [120, 156] is a valid zlib header
let result = deflate(&[120, 156, 3, 0, 0, 0, 0, 1]); // Minimal valid zlib stream
assert!(result.is_ok());
}