First real CLI in Rust (~40K LOC) - would love feedback on patterns and architecture

51 points ToiletSenpai 26 comments

built a code intelligence tool in rust. it parses codebases, builds a graph, and lets you query it. kind of like a semantic grep + dependency analyzer.

this is my first serious rust project (rewrote it from python) and i'm sure i'm committing crimes somewhere. would love feedback from people who actually know what they're doing.

repo: https://github.com/0ximu/mu

what it does

bash

mu bs --embed          
# bootstrap: parse codebase, build graph, generate embeddings
mu query "fn c>50"     
# find complex functions (SQL on your code)
mu search "auth"       
# semantic search
mu cycles              
# find circular dependencies
mu wtf some_function   
# git archaeology: who wrote this, why, what changes with it

crate structure (~40K LOC)

Crate LOC Purpose
mu-cli 23.5K CLI (clap derive), output formatting, 20+ commands
mu-core 13.3K Tree-sitter parsers (7 langs), graph algorithms, semantic diff
mu-daemon 2K DuckDB storage layer, vector search
mu-embeddings 1K BERT inference via Candle

key dependencies

parsing:

  • tree-sitter + 7 language grammars (python, ts, js, go, java, rust, c#)
  • ignore (from ripgrep) - parallel, gitignore-aware file walking

storage & graph:

  • duckdb - embedded OLAP database for code graph
  • petgraph - Kosaraju SCC for cycle detection, BFS for impact analysis

ml:

  • candle-core / candle-transformers - native BERT inference, no python runtime
  • tokenizers - HuggingFace tokenizer

utilities:

  • rayon - parallel parsing
  • thiserror / anyhow - error handling (split between lib and app)
  • xxhash-rust - fast content hashing for incremental updates

patterns i'm using (are these idiomatic?)

1. thiserror (lib) vs anyhow (app) split:

rust

// mu-core (library): thiserror for structured errors
#[derive(thiserror::Error, Debug)]
pub enum EmbeddingError {
    #[error("Input too long: {length} tokens exceeds maximum {max_length}")]
    InputTooLong { length: usize, max_length: usize },
}

// mu-cli (application): anyhow for ergonomics
fn main() -> anyhow::Result<()> { ... }

2. compile-time model embedding:

rust

pub const MODEL_BYTES: &[u8] = include_bytes!("../models/mu-sigma-v2/model.safetensors");

single-binary deployment with zero config. BERT weights baked in. but... 140MB binary.

3. mutex poisoning recovery:

rust

fn acquire_conn(&self) -> Result<MutexGuard<'_, Connection>> {
    match self.conn.lock() {
        Ok(guard) => Ok(guard),
        Err(poisoned) => {
            tracing::warn!("Recovering from poisoned database mutex");
            Ok(poisoned.into_inner())
        }
    }
}

4. duckdb bulk insert via appenders:

rust

let mut appender = conn.appender("nodes")?;
for node in &nodes {
    appender.append_row(params![node.id, node.name, ...])?;
}
appender.flush()?;

things i'm least confident about

1. 140MB binary size

model weights via include_bytes! bloats the binary. considered lazy-loading from XDG cache but wanted zero-config experience. is this insane?

2. constructor argument sprawl

rust

#[allow(clippy::too_many_arguments)]
pub fn new(name: String, parameters: Vec<ParameterDef>,
           return_type: Option<String>, decorators: Vec<String>,
           is_async: bool, is_method: bool, is_static: bool, ...) -> Self

should probably use builders but these types are constructed often during parsing. perf concern?

3. graph copies on filter

find_cycles() with edge type filtering creates a new DiGraph. could use edge filtering iterators instead but the current impl is simpler.

4. vector search is O(n)

duckdb doesn't have native vector similarity, so we load all embeddings and compute cosine similarity in rust. works for <100K nodes but won't scale.

5. thiserror version mismatch

mu-core uses v1, mu-daemon/mu-embeddings use v2. should unify but haven't gotten around to it.

would love feedback on

  • is the thiserror vs anyhow split idiomatic?
  • builder vs many-args constructors for AST types constructed frequently?
  • better patterns for optional GPU acceleration with candle?
  • anyone using duckdb in rust at scale - any gotchas?
  • tree-sitter grammar handling - currently each language is a separate module with duplicate patterns. trait-based approach better?

performance (from initial benchmarks, needs validation)

repo size file walking
1k files ~5ms
10k files ~20ms
50k files ~100ms

using ignore crate with rayon for parallel traversal.

this is genuinely a "help me get better at rust" post. the tool works but i know there's a lot i could improve.

repo: https://github.com/0ximu/mu

roast away. 

El Psy Kongroo!