Logo
Logo
4 results for
  • I’ve seen two extremes in Rust error handling. On one end: Box<dyn std::error::Error> everywhere, a stringly-typed mess where you can’t distinguish a network timeout from a parse failure. On the other end: 47 custom error types with hand-written Display and From implementations, an over-engineered cathedral of boilerplate.

    The right answer is somewhere in the middle. And the two crates that get you there are thiserror and anyhow.


    The Wrong Ways

    Wrong Way #1: String Errors

    fn parse_config(input: &str) -> Result<u16, String> {
        let port: u16 = input.parse().map_err(|e| format!("bad port: {}", e))?;
        if port < 1024 {
            return Err("port must be >= 1024".to_string());
        }
        Ok(port)
    }
    

    The caller gets a String. What can they do with it? Print it. That’s about it. They can’t match on error variants, can’t programmatically decide how to handle different failures, can’t distinguish between “bad port” and “port too low.” It’s a dead end.

    Rust tutorial rust idiomatic-rust Created Mon, 29 Apr 2024 12:18:00 +0000
  • I used to treat TCP as a black box. Data goes in one side, data comes out the other — reliably, in order, no duplicates. That was all I needed to know, right? Then I started debugging latency spikes in a payment service and spent three days chasing a 200ms tail latency that turned out to be Nagle’s algorithm fighting with delayed ACKs. After that, I stopped treating TCP as a black box.

    fundamentals networking Created Mon, 29 Apr 2024 00:00:00 +0000
  • Linked lists are the most over-taught data structure in computer science and the most under-used structure in production systems. I’ve reviewed hundreds of pull requests across distributed systems codebases, and I can count on one hand the times a linked list was genuinely the right call.

    That said, understanding why linked lists are usually wrong teaches you something profound about how computers actually work. And there are a handful of situations where they’re exactly right — and when those situations come up, you need to recognize them fast.

    fundamentals data structures Created Sun, 28 Apr 2024 00:00:00 +0000
  • There’s a function signature dilemma that every Rust developer hits: should this function return &str or String? If you return &str, you avoid allocation — but sometimes you need to create a new string. If you return String, you allocate every time — even when you don’t need to.

    Cow says: “Why not both?”

    The name stands for “Clone on Write,” and it’s one of those types that seems weird until you use it — and then you wonder how you lived without it.

    Rust tutorial rust idiomatic-rust Created Sat, 27 Apr 2024 15:33:00 +0000
  • At some point every program needs to persist something — a config file, a log, a data export. File I/O is one of those topics that sounds tedious but is genuinely satisfying once it clicks. Go gives you a few different layers to work with, from the low-level os package to convenient one-liners. I’ll show you all of them so you can pick the right tool for the job.


    The Basics

    Reading a file the simple way

    For most cases where the file is small enough to fit in memory, os.ReadFile is all you need:

    Go tutorial golang beginner Created Fri, 26 Apr 2024 00:00:00 +0000
  • Here’s something that confused me for weeks when I started Rust: I’d write a function that takes &str, and I could pass it a &String. I’d write a function that takes &[i32], and I could pass it a &Vec<i32>. I’d use * to dereference a Box<T> and get a T.

    How? Why? The answer is Deref coercion — one of Rust’s most elegant features, and one of the most poorly explained.

    Rust tutorial rust idiomatic-rust Created Thu, 25 Apr 2024 21:10:00 +0000
  • When I first started using Go seriously, I accepted goroutines as “lightweight threads” without really understanding what that meant. The Go runtime creates them, schedules them, and I launch them with go. Then I got curious: what does the OS actually see? When I run 10,000 goroutines, does the kernel manage 10,000 things? The answer is no — and understanding why requires understanding the difference between processes, kernel threads, and userspace threads. It also explains why goroutines scale so much better than Java threads or Python threads.

    fundamentals linux operating systems Created Thu, 25 Apr 2024 00:00:00 +0000
  • The first time I saw .into() in Rust code, I was baffled. “Into what?” There was no type annotation, no explicit conversion function, just .into() hanging off a value like it knew exactly what to become. And somehow the compiler figured it out.

    That’s the From/Into trait pair. It’s one of the most-used patterns in idiomatic Rust, and once you understand it, your APIs will feel dramatically smoother.


    The Basics: From and Into Are Mirror Images

    // If you implement From<A> for B...
    impl From<i32> for MyType {
        fn from(value: i32) -> Self {
            MyType(value)
        }
    }
    
    // ...you get Into<B> for A for free
    let x: MyType = 42.into();
    

    Always implement From, never Into directly. The standard library provides a blanket implementation: if From<A> exists for B, then Into<B> is automatically available for A. Implementing Into directly doesn’t give you the reverse.

    Rust tutorial rust idiomatic-rust Created Wed, 24 Apr 2024 07:55:00 +0000
  • Every Rust dev eventually runs into the “struct with 12 fields” problem. You’ve got a configuration type. Some fields are required, some are optional, some have sensible defaults. In Java, you’d use a Builder. In Python, you’d use keyword arguments. In Go, you’d use functional options.

    In Rust? You have options. And one of them is clearly better than the rest.


    The Problem: Constructor Explosion

    struct ServerConfig {
        host: String,
        port: u16,
        max_connections: usize,
        timeout_secs: u64,
        tls_enabled: bool,
        log_level: String,
        workers: usize,
    }
    
    // This is painful
    fn main() {
        let config = ServerConfig {
            host: String::from("0.0.0.0"),
            port: 8080,
            max_connections: 1000,
            timeout_secs: 30,
            tls_enabled: false,
            log_level: String::from("info"),
            workers: 4,
        };
    }
    

    Every field must be specified. There’s no concept of “default.” If you add a new field later, every construction site breaks. And callers have no idea which values are “important” and which are “just use the default.”

    Rust tutorial rust idiomatic-rust Created Mon, 22 Apr 2024 19:42:00 +0000
  • Every time I call db.QueryContext(ctx, "SELECT * FROM orders WHERE user_id = $1", userID) in Go, I used to think the database just “found” the rows. It wasn’t until I started debugging a production slowdown — a query that was fast for months and then suddenly wasn’t — that I actually traced what happens between the moment my application hands off that SQL string and the moment rows come back. Understanding that pipeline changed how I write queries, design schemas, and diagnose performance problems.

    fundamentals databases Created Mon, 22 Apr 2024 00:00:00 +0000