Created: 2026-02-09 Updated: 2026-03-10 16 min read

This is my 40-day devlog:

// just map the error and use a closure to do something
blah_blah.map_err(use a closure or do something with this)

for handling an option:

// providing a fallback value
value.unwrap_or("default")
// (closure computes only if None)
value.unwrap_or_else(|| compute_default())
// convert option -> result so you can use ? 
// and propagate the error upwards
value.ok_or_else(|| MyError::NotFound)?

days like this I miss golang a lot. I had no problem with err != nil but one annoying part in go is if you encounter an error you still have to return the corresponding empty value along with the error and I hated it in go but here in rust I feel returns are handled better. eg:

func a() (s string, err error) {
    result, err := somethingThatCanFail()
    if err != nil {
        // You're forced to return a zero/empty value alongside the error
        // For a string it's "", for a struct it's annoying to find the 
        // definition and return an empty
        // if I return nil instead of empty initialized we should
        // make sure thats handled it else panic
        return "", err
    }
    return result, nil
}

but in rust:

fn a() -> Result<String, SomeError> {
    // ? propagates the error and returns early on Err so no 
    // empty value needed
    // ? operator is nothing but a short notation for match 
    // command to match value and error, 
    // if error propagates upwards
    let result = something_that_can_fail()?;
    Ok(result)
}

day 11

continued my epub internals exploration. my software is slowly shaping up. One goal I had in mind was to embrace the language’s functional patterns. I usually avoid using functional patterns and do the grunt work instead, mainly because I was lazy to learn and adapt to the pattern. so today, this was the scenario I had to code:

 let r =new::vec() // initialized empty container

  run a for loop{
    
    do a bunch of transformations
    append the end result of transformation into r
  }
  return r

I knew there existed some functional pattern I could leverage. did some digging and found this

let r = iterator.map(|some closure| =>{
    perform whatever
})collect::<type>();

for example:

items.iter().map(|item| ->Result<somestruct,error>{
Ok(somestruct{})}).collect::<Result<Vec<_>,_>>()

this is a super cool pattern I learned today. collect solves a lot of manual typing and makes it look cleaner but I still feel it adds some cognitive strain compared to straightforward looping and collecting, despite the additional lines. This is just my personal bias. for me the number one metric of code quality is cognitive load when someone reads the code. just thinking about cognitive load solves all other code quality related problems for me like naming, clean code…whatever! I also learnt about what _ does. it just smartly identifies the type at compile time. it’s a placeholder for a concrete type that rust already knows from context. I had a few rust CRUDs written so I quickly vibecoded a very basic frontend and asked claude to connect the frontend with my written backend. I want to learn typescript as well but for now I am focusing purely on improving my rust skills. so all my rust code is handwritten and I am planning to use basic simple vibecoded typescript for frontend. mindboggling how claude is able to one shot beautiful frontend which I could never do without llm’s help. One problem I have with vibe coding typescript is I am not literate enough in typescript to judge the code it generates. so eventually I’m thinking of quickly going through typescript docs (as I already know a little bit of react) to understand the generated code better because vibecoding feels very uneasy to me.

day 12

with the gained knowledge of epub I finalised that I need to extract the spine out of epub to get the chapters, wrote spine extraction from epub using epub crate which fortunately works out of the box. today I learnt the difference between iter() and into_iter(). so assume you are returning a struct after conversion:

Ok(some_container.iter().map(|item]->some_struct{
    fill the struct with reference &val
 })
)

this is actually wrong because iter borrows not moves so we can access the reference only and returning will go out of its lifetime. so in cases like these we can do a ownership change using into_iter() which will transfer the ownership so we can return the struct

day 13

Since I am vibecoding the TypeScript frontend, I can’t trust the code AI writes. I need to make sure I validate and sanitize every single thing that comes to the backend at the backend entrypoint (handler). There are many ways to skin this cat but I felt we could do type-driven validation and sanitization by utilizing the constructor and access specifiers in Rust to enforce validation. we can either define custom types (tuple struct or whatever). I started to use this pattern extensively in model.rs

pub struct StructA(usize);

impl StructA {
    pub fn parse(v: usize) -> Result<Self, ApplicationError> {
        if v > 10_000 {
            return Err(ApplicationError {
                    // error is handled here
            });
        }
        Ok(Self(v))
    }
    pub fn get(&self) -> usize {
        self.0
    }
}

pub struct StructBRequest {
    file_id: uuid::Uuid,
    spine_idx: StructA,
}

impl StructBRequest {
    pub fn validate(raw: StructBRaw) -> Result<Self, ApplicationError> {
        Ok(Self {
            file_id: raw.file_id,
            spine_idx: StructA::parse(raw.spine_idx)?,
        })
    }
    pub fn file_id(&self) -> uuid::Uuid {
        self.file_id
    }
    pub fn spine_idx(&self) -> usize {
        self.spine_idx.get() 
    }
}

take a look at this code, we have 3 structs here: StructA which is a tuple struct, StructBRequest which is the one that stores validated and sanitized input, StructBRaw is the raw input from the UI before validation. StructA is public but you can see that usize is not public, so you can access an object of the structure but not the values in it from other modules. similarly in StructBRequest, even though the struct as a whole is public, thanks to Rust’s access specifiers, file_id and spine_idx are private other modules cannot access these values directly and have no choice but to use file_id() and spine_idx() to get the values, which are validated via validate(). let’s name the module which calls this struct: call.rs

// import the structures from model.rs
// assume this handler is called by the ui
fn handler(raw: StructBRaw)->Result<(),ApplicationError>{
    let req=StructBRequest::validate(raw)?;
    // req is the object of the struct but req.file_id will
    // throw error because file_id is private
    // so how can we access the fields?
    let file_id = req.file_id();
    // note: req.file_id() is same notation wise as 
    // StructBRequest::file_id(&req)
    let spine_idx = req.spine_idx();
}

by doing this you have no choice but to validate. forcing validation is the key here. we are not giving an option to validate or skip, rather we have designed it in such a way that there is no other option to proceed other than validating the incoming struct.

day 14

today I sat and did a bunch of TypeScript vibecoding. I was told frontend is a solved problem for AI but looks like it is 95% close and I have to drive the last 5%, else the models drink a crazy amount of tokens. nevertheless I am mind-blown with the kind of screens these models are able to generate. If not for LLMs I would have taken at least a couple of weeks to build it from scratch. I am still worried about the amount of TypeScript code it generated. I am very sure that if I do it myself it would be a lot less. But the functionality works and I guard/validate and sanitize everything at the backend entry so I am fine with it for now because frontend is not my priority now. I went through a couple of good quality OSS TypeScript projects and learnt about how the files are structured and quickly structured the code. we are 90% close to finishing the application

day 15

I am done with v0 of the RSVP ebook screen reader. so today I spent my time figuring out how to cross-compile and distribute the Tauri application. one disadvantage of Tauri over Electron is you can’t cross-compile to a different OS from one system. this is a direct consequence of Tauri’s biggest advantage. unlike Electron, Tauri uses the OS’s native WebView (WKWebView on macOS, WebView2 on Windows, and WebKitGTK on Linux) so it’s not bundled with the binary. Electron, on the other hand, ships Chromium and the Node.js runtime together with every app, making Electron apps significantly heavier. since the WebView is OS-native, I have no other choice but to use different runners (Linux, Mac, and Windows) to natively compile the application on each OS and release them. thanks to free GitHub runners for public repos, I wrote a simple GitHub Actions workflow that, upon releasing a new tag, automatically starts a build that compiles and creates a draft release. also wrote a simple Makefile to trigger all of these because I am not sure when I will get back to this again. having a Makefile with all commands tied to it is very useful because if I revisit this after months, it’s easier for me to pick things up and run deployments. finally binary size is 4ish MB. successfully release V0. here is a demo video

day 16

I use a Debian stable machine and everything works fine for me but I want to test it on other devices. I asked one of my friends who uses a Mac to test the beta version. he is my go-to person every time I build something. since he is semi-technical he uses whatever I build from an end user perspective. He gave me a bunch of UX feedback, asked for a couple of usability features and he found a couple of bugs. I was very surprised these frontend-specific bugs existed because I tested those scenarios while I built those features. so when the LLM made some changes in the UI it messed this up. This was a revelation for me. from now on if I ever vibe code the frontend, I will definitely have a UI testing setup like playwright. for now I fixed all the bugs and UX features he asked for and redeployed. I built this for myself so I will be using it every day to read ebooks and whenever I need something I am thinking of quickly adding it in. I was asked to build something in golang for a while now so onto the next one :)