This is my 40-day devlog:
the whole idea behind devlog is its very cool
I think devlog will be a great way to get enough data to make good decisions and understand my learning patterns.
disclimer: none of it is ai written.

day 1 (feb 9 2026)
started with zero to rust in production chapter 6.learned about Result<(),error type> type in rust. this is a really cool pattern of error handling. Very explicit and straight forward with must check errors.hate the python way of try catch,go felt better than python and this rust way feels even better. Got introduced to type driven development, similar to what they do in functional programming to validate input fields. I don’t fully grasp the idea yet and I need to deep dive and understand this pattern
day 2
quickly went through the second half of chapter 6 from zero to rust in production. new crates keep popping up all the time. legit feels like there are 10 features in rust but 10 million crates and libraries that needs to be understood and glue the pieces together. I Like how rust is static linked language similar to golang so I can use a multistage build for deployment. I was able to get an extremely small docker image using multistage build thanks to static linking!
day 3
as part of understanding type driven development, learnt about the famous parse,dont vaildate. tried to implement this parse,dont validate pattern. newtype pattern came handy to implement this. validation is when you check for some data and return a bool if validates and parsing is when you validate but also transform it to whatever type needed and return (model the data), took some time for me to understand this today. Also started with chapter 7 of zero to production in rust book.
day 4
I went into the rabbit hole of direct string .clone() and clone with arc pointer arc::new()….how a direct clone will create a complete new string but in arc pointer we can have 2 variables which can point to the same value but as per rust’s borrowing rule both of it becomes immutable reference. continued with chapter 7 of zero to production in rust. this chapter talks way too much about writing tests!! just like c++ got to know rust has smart pointers and they are very similar to c++ smart pointers. but need to deep dive into it tomorrow.
day 5
Wrapped up with zero to rust in production chapter 7. got super bored with this book. since tomorrow is weekend (saturday) I am thinking of building something small and quick all by myself. after bunch of research I have finally decided that I will be building a small tiny desktop application using tauri. This will be a good exercise because now I feel I am all over the place with respect to rust.
day 6
beautiful saturday. Started the day with going through tauri docs. what a lovely piece of technology. the docs were so user friendly I was able to understand most of what tauri is trying to do. Designed the desktop software end to end which I am going to build. I am equally illiterate with typescript. I know react a bit but I had no idea with typescript. In fact today is the first time I google whether typescript is a frontend or backend language lol. got to know it’s a type safety react? so decided: frontend of this software will be typescript despite I am not going to focus much on frontend and backend will be rust. for me the easiest way to understand a new language structure is to go through other production open source software built in that tech stack. this helps me to structure my codebase better. quickly googled up sample tauri v2 applications went through their code structure, picked the best structure which suits me, which I felt the correct approach and finally wrote my first frontend handler. tomorrow I should iterate!
day 7
made some more progress on the desktop app I am building. Recent past I am writing lot of python and I really like their error handling. I went back to go style error handling. create error enums and a error struct Application which Result<(),ApplicationError> would propagate. ApplicationError is a struct with error code (enum) and a error message for uniformity. since I am going to build a desktop app I need access to a folder in filesystem. figured out how to create one and luckily tauri gives this feature by default (tauri automatically chooses the directory location still configurable based on the os). I wanted a really small database in this project, googled and found out about sqlx crate. sqlx rust has lot of postgres example but for my usecase sqlite is the perfect choice but sqlx sqlite docs support is really poor. somehow figured it out and did a init db setup alone for today.
day 8
today spent my free time experimenting with port forwarding in my router. Started writing a blog on it.
day 9
most productive day in 2026 till now. Although I am able to perform at the top of my game in my day job coding, I am struggling with productivity for a few months now with my recreational programming and I never took steps to rectify it because I felt recreational programming has to be recreational and not forced. But it got so bad that I am starting to feel that if I have a rough structure/process then I can learn a lot more without fatigue or guilt. I have heard about Pomodoro style productivity for a while now and it never appealed to me because of the breaks in between coding sessions. I always had this pseudo productivity notion that long coding hours = better productivity so 25 min sessions won’t work for me and breaking in between sessions would affect my flow. So I never gave Pomodoro a try until today. I actually felt better today after trying it out. I used pomfocus’s with its default configuration. 25 min coding session, 5 min break and after every 4th cycle we can take a 15 min break. One small modification which I did is instead of hard stopping at 25 min, if I am in a good flow state I would keep coding but the timer is still 5 min for a cycle, 15 min after the 4th cycle. This gives me the best of both worlds. I did 7 cycles today. I aim to hit 3-4 cycles minimum on weekdays, let’s see how it goes. Back to coding. Today I dived deep into internals of epub for the desktop app I am building. epub is a fascinating technology especially the concept of spine in epub. epub is nothing but a ZIP archive of files, where the spine (which is not the TOC) is the source of truth for the sequential page reading order. I searched for a while and found out this underrated epub crate which has all the rudimentary primitives of accessing epub’s internals through which we can manipulate and access epub resources. got to know about to_string_lossy(). sometimes strings might have random bytes which are not valid unicode and therefore not readable. so we have to safely handle this. this is done with to_string_lossy() where if something cannot be converted to a string, it replaces it with a placeholder. rust strings (String, &str) must be valid UTF-8. But many things in operating systems are just raw bytes: like file paths, filenames, zip archive entries which might contain invalid utf-8. I have never thought about this before; in golang I would do to_string() and everything converts to string and I never cared about the non-characters. I was debugging but couldn’t figure out why the code compiles, runs, is logically correct but it panics. after a while figured out that it is due to the todo!() macro. if code reaches the todo!() macro at runtime it will panic because it is not implemented yet. I thought the todo!() macro is just like a comment TODO. funny lol.
day 10
did a simple sqlite setup, wrote basic migration. bound sqlx into the binary so that it is easy to boot up the database bootstrap for my simple software. I love sqlite. for me postgres is painful to do quick setup and develop. I did the rustlings challenge a while back and the challenge extensively used the unwrap() pattern. got to know that if you call .unwrap() on an Option::None or a Result::Err, it panics. today I wrote code which gave None as output and I tried to unwrap and it panicked. learned that the better way to handle errors is: for handling a result:
// 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 :)
