{"slug": "work-in-progress-rust", "title": "Work In Progress Rust", "summary": "A Rust developer at Meilisearch presents techniques for deferring error handling during development, such as using `unwrap` and `clone` to bypass compilation errors temporarily, and relying on CI with `-D warning` to catch warnings before merging. The approach allows developers to stay in the \"happy path\" while prototyping, then address correctness later.", "body_md": "Rust is a reliable language that prioritizes correctness. However, during development, it can be a burden to account for every single error path immediately.\n\nThis article presents a few techniques that defer handling correctness to that you can stay longer in the happy path, as well as a library I developed to make this process more convenient.\n\n🌱 100% of this article has been handcrafted without using generative AI tooling, if somewhat hastily. Take my typoes and broken sentences as proof of that.\n\nWhat's worse for a Rust developer working on some elaborate logic than having `rustc`\n\nsuddenly getting in the way?\nBe it because you didn't return a `Result`\n\nyet, or handle this error case, or implement that edge case, causes for compilation errors\nare numerous and can distract you from what you're attempting to achieve.\n\nWhen this happens you're usually presented with two options:\n\n- Go with the distraction and immediately implement perfect error handling. Typically you'll have to refactor it a number of times before the PR is ready, making the whole process much more heavy than what is necessary.\n- Slap some manner of\n`TODO later`\n\nlabel on the error, downgrading it to a warning, and move on with your work for now.\n\nThis article covers some Rust techniques to achieve the latter option, downgrading an immediate error to a warning to be handled later.\n\n[Why do you want warnings?](#why-do-you-want-warnings)\n\nWarnings occupy the perfect spot in the possible gamut of compilation interactions:\n\n-\nErrors block other compilation steps (lifetimes, etc), and testing, impeding the common workflow:\n\n- Write WIP code\n- Test it\n- Turn WIP code to production code\n\nAny error prevents going from (1) to (2)\n\n-\nCompletely suppressing any compilation message, on the other hand, runs the risk of forgetting about the issues and letting them sweep into production 😬\n\n-\nWarnings don't block development, but need to be cleaned before merging the PR.\n\nAt Meilisearch, we have a CI that runs the compiler with the\n\n`-D warning`\n\nflag, that will turn all warnings into errors at CI time, ensuring we don't ship WIP code accidentally.\n\n[Why don't you just read the f**king code???](#why-don-t-you-just-read-the-f-king-code)\n\nOf course, **I do**.\nReading code (yours, and the one from your colleagues) is [an excellent barrier against bugs](https://entropicthoughts.com/code-reviews-do-find-bugs),\nand code reviews are a requirement to effectively share knowledge among your team.\n\nAll the techniques discussed in the article come as an *additional* manner of creating incentives for correctness and stronger guardrails\nwhen producing code at a professional scale. They increase quality but don't replace the basic good practices.\n\nIn my experience as a software engineer, anything that relies on humans never a mistake is deemed to fail eventually.\n\nWithout further ado, here are some techniques you can use in vanilla Rust to implement error deferral strategies.\n\n[Techniques with standard Rust](#techniques-with-standard-rust)\n\n`unwrap`\n\neverywhere\n\n`unwrap`\n\neverywhereWhile it is important that Rust gives us the structure to correctly address errors, it doesn't mean we need to address them immediately.\n\nA common pattern for delaying the error handling is to simply `unwrap`\n\nyour `Result`\n\nor `Option`\n\n, to get the\nvalue inside at the price of a panic on the error path.\n\nThen when the rest of the code is ready, you can go back to your changes and selectively remove the `unwrap`\n\nthat were added as a temporary\nmeasure, replacing them with proper error handling.\n\n``` php\nfn does_not_return_result_yet(some_params: SomeType) -> SomeReturnType {\n  let converted = some_params.try_into().unwrap() // <- fallible conversion, ignore for now\n  converted.try_foo().unwrap() // <- fallible operation, ignore for now\n}\n```\n\n`clone`\n\neverywhere\n\n`clone`\n\neverywhereOwnership is a similar issue, albeit slightly different and much less common than error handling. Properly maintaining ownership sometimes mandates design modifications, lest lifetime issues appear. In this case, a frequent mitigation option is to clone a value instead of moving it around.\n\nDelaying the fixes for this category of issues is a bit more delicate, as the fix can entail changing the architecture of the code in non-trivial ways. Still, with experience, it is possible to identify early cases where you'll be able to remove the clones later by passing the appropriate variables higher in the stack.\n\n[the ](#the-todo-macro)`todo!`\n\nmacro\n\n`todo!`\n\nmacroThe Rust standard library provides a [ todo!() macro](https://doc.rust-lang.org/std/macro.todo.html), whose purpose is described as:\n\nIndicates unfinished code.\n\nThis can be useful if you are prototyping and just want a placeholder to let your code pass type analysis.\n\nThis is indeed extremely useful when shaping out an interface and needing to make some function definitions available, without actually having to provide a working implementation.\n\nBecause `todo!`\n\ndiverges (it panics), the compiler is able to use it as a placeholder expression for most types,\nhence the reference to type analysis in the docs.\nA liberal use of `todo`\n\nallows deferring some code paths (and not just errors) to be implemented at a later time.\n\n[Digression: not to be confused with ](#digression-not-to-be-confused-with-unimplemented)`unimplemented!()`\n\n`unimplemented!()`\n\nI saw at least once someone [being wrong on the Internet](https://xkcd.com/386) about `todo!()`\n\n:\nthey would state that it was meant to communicate to users of a program that some features were not yet implemented.\n\nRust actually has a dedicated macro for this use case,\nand its name is [ unimplemented!()](https://doc.rust-lang.org/std/macro.unimplemented.html).\n\n`todo!()`\n\nhas a different audience: the author of the code and their reviewers. No `todo`\n\ns are meant to hit production, they are a\nWIP Rust amenity only.\n\n`mut`\n\n, unused variables, etc.\n\n`mut`\n\n, unused variables, etc.The default warnings provided by the compiler are very useful while developing. They will catch:\n\n- unused bindings\n- needlessly mutable bindings\n- unused results and options\n\nAll of these tend to appear when code isn't finished developing. For instance, a function whose implementation consists solely of `todo!()`\n\nwill\ntypically have such warnings about their parameters.\n\n⚠️It is important to resist the urge to **temporarily fix these warnings during development**,\nby prefixing the bindings with `_`\n\nor other tricks. You risk handing over the unfinished code if you artifically fix the warnings.\nIgnoring warnings has its place, but it ain't that!\n\n[Leaving a memo: the doc comment trick](#leaving-a-memo-the-doc-comment-trick)\n\nIt often happens while I'm coding that I think of something that needs fixing or handling, like an edge case or some incomplete/broken logic. In contrast with explicit error handling, this kind of issues is not tracked in the type system, so I usually leave a comment with a reminder to fix the issue, if I don't want a distraction from the logic I'm currently implementing.\n\nThis is fine and dandy, but I need to remember to actually go back there after the fact 😅\n\nAs a means to do this, I often use a trick where such comments are actually prefixed with a triple slash,\nas if they were [documentation comments](https://doc.rust-lang.org/reference/comments.html#r-comments.doc.syntax).\n\nRust warns about documentation comments that don't document anything, so putting a documentation comment above a statement generally achieve the goal of generating a warning associated with the issue described in the comment.\n\n[Where are my warnings? Limitations of vanilla Rust](#where-are-my-warnings-limitations-of-vanilla-rust)\n\nThese techniques served me well for years (I've been writing Rust for more than a decade now...), however I have my grievances with them.\n\nGenerally, the most aggravating issue is that not all of these techniques reliably generate a warning associated with the issue that needs solving before sending the code into production.\n\nForgetting to remove an `unwrap`\n\nand watching it burst into flames after the release is one of the worst feelings\nyou can get as an engineer 😓\n\n`todo!`\n\nis a frequent and weird offender in this category. As stated earlier, it often results in warnings, but these are indirect and caused\nby e.g. function parameters going unused or mutation not taking place when it should.\nBecause of this, it can become a bit treacherous, as one is used to often have a warning when using `todo!`\n\nand rely on their absence\nto hit the merge button.\n\n`unwrap`\n\nand `clone`\n\nare even worse, they never emit warnings, so you have to be extra careful in re-reading to remove them\nbefore the final PR.\n\nAn additional issue with them is that\nsome of them are [ meant to reach production](https://burntsushi.net/unwrap/).\nWith the \"unwrap liberally\" strategy outlined in this article, we are conflating the normal use case of unwrap with its \"WIP\" use case.\n\nLastly, \"TODO doc strings\" are kind of a hack, perhaps we could say it is a happy little accident?\nIn this capacity, it has unintuitive failure modes, because doc comments are currently forbidden on expressions,\nso will cause a compilation error.\nDoesn't frequently happen, but we also run the risk of using the `///`\n\nin locations where they actually document something,\nwhich isn't exactly nice to ship and won't have the expected warning.\n\n[Introducing the ](#introducing-the-wip-crate)`wip`\n\ncrate\n\n`wip`\n\ncrateIn response to all these small pains for writing WIP Rust, I decided to develop [a crate called wip](https://codeberg.org/dureuill/wip/) to make it easier to implement the \"warnings for later\" strategy.\n\nMostly it consists of a collection of tools you can use in every context while you're developing Rust.\n\n[Using ](#using-wip)`wip`\n\n`wip`\n\nAdd `wip`\n\nto your dependencies like you would for any other crate:\n\n```\ncargo add wip\n```\n\nOptionally, you can import the prelude provided by `wip`\n\nin the files where you want to use `wip`\n\n:\n\n```\nuse wip::prelude::*;\n```\n\nAs `wip`\n\nrelies on the deprecation warnings of Rust, I suggest you disable the feature of your editor to\nstrike calls to deprecated functions, otherwise it becomes visually invasive.\n\n`wip::wip!`\n\n, a warning-emitting `todo!`\n\n`wip::wip!`\n\n, a warning-emitting `todo!`\n\nThe first tool provided by `wip`\n\nis a `wip!`\n\nmacro, that works exactly like `todo!`\n\n, but always emits a warning on use.\nUse it everywhere you'd use `todo!`\n\n, to mark code as unfinished.\n\n[Why not ](#why-not-todo)`todo!`\n\n?\n\n`todo!`\n\n?Earlier versions of `wip`\n\nused the `todo`\n\nnaming, however it would prevent us from using the prelude,\nas Rust doesn't like it when a dependency attempts to shadow an `std`\n\nmacro...\n\n`unwrap_wip`\n\nand `clone_wip`\n\n`unwrap_wip`\n\nand `clone_wip`\n\nThese methods are implemented as extension traits on `Result`\n\nand `Option`\n\n. When in scope, they\nwork exactly like the regular `unwrap`\n\nand `clone`\n\n, except that they emit a warning on usage.\n\nThey also clearly signal that you don't mean for the unwrap/clone to stay in final code! Although, keep in mind it doesn't always end up happening that way...\n\n`wip::fixme!`\n\n, a non-panicking macro for memos\n\n`wip::fixme!`\n\n, a non-panicking macro for memosOne of the tools I use the most from `wip`\n\nis the `fixme!`\n\nmacro. It is a non-panicking variant of `wip!`\n\n,\nthat doesn't stand for an expression. It works the same as TODO comments, sans the hackish part and with a reliable warning.\n\n[Embracing our WIP future](#embracing-our-wip-future)\n\nI have been using the `wip`\n\ncrate for a few of the my latests PRs, including some of my more [unreasonably large ones](https://github.com/meilisearch/meilisearch/pull/6484).\nUsually I add the `wip`\n\ndependency to crates in the workspace during development, and then remove it when rewriting history before review.\n\nIt has been a liberating experience, squarely falling in the category of \"I didn't expect to need it *that much*\".\nIt makes focusing on the happy path much more straightforward, while not forgetting things for later.\n\nWhat's not to love?\n\nOnly drawbacks I can think of:\n\n- One needs to write enough context when adding a\n`fixme`\n\n, otherwise they can become hard to fix when coming back much later. This is usually not a problem, as`wip`\n\nare usually contained to (usually) short PRs by a single author (or pair programmed). - Sometimes the number of\n`wip`\n\n-induced warnings can become a bit discouraging, again especially on larger PRs. Perhaps avoid those 😅\n\nStill, I'm quite sure I'll keep using `wip`\n\n. How do you use warnings during development? What do you think would be some useful additions to the [current API](https://docs.rs/wip/latest/wip/)?", "url": "https://wpnews.pro/news/work-in-progress-rust", "canonical_source": "https://blog.dureuill.net/articles/wip/", "published_at": "2026-07-05 17:28:12+00:00", "updated_at": "2026-07-07 00:38:17.316011+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Meilisearch", "Rust"], "alternates": {"html": "https://wpnews.pro/news/work-in-progress-rust", "markdown": "https://wpnews.pro/news/work-in-progress-rust.md", "text": "https://wpnews.pro/news/work-in-progress-rust.txt", "jsonld": "https://wpnews.pro/news/work-in-progress-rust.jsonld"}}