Live data from Hacker News

Rust errors without dependencies

vincents.dev

71–80 of 91 posts

Re: Rust errors without dependencies

#71

> I want less code. I want to limit the amount of 3rd party code I pull in. This is mostly due to supply chain disasters over on NPM scaring me and the amount of code dependencies bringing in see rust dependencies scare me. `anyhow` has exactly one optional dependency (backtrace). `thiserror` has three (proc-macro2, quote, syn) which are at the base of practically the entire Rust ecosystem. Unless the author has zero…

nit: `thiserror` has 4 dependencies (proc-macro2, quote, syn, unicode-ident). Indirect dependencies are still dependencies.

Re: Rust errors without dependencies

#72
post #61

Earlier quoted context omitted.

Why is it an extensibility hazard (assuming you mark the `pub enum Error` as non-exhaustive)? I mean I don't see the difference between having the non-exhaustive enum at the top level vs in a subordinate 'kind'.

Take the example at https://docs.rs/thiserror/latest/thiserror/ - Struct variant fields are public, limiting how you evolve the fields and types - Struct variants need non_exhaustive - It shows using `from` on an error. What happens if you want to include more context? Or change your impl which can change the source error type None of this is syntactically unique to errors. This becomes people's first thought of what…

> Struct variant fields are public, limiting how you evolve the fields and types

But the whole point of thiserror style errors is to make the errors part of your public API. This is no different to having a normal struct (not error related) as part of your public API is it?

> Struct variants need non_exhaustive

Can't you just add that tag? I dunno, I've never actually used thiserror.

Your third point makes sense though.

Re: Rust errors without dependencies

#73
post #62
post #61

Earlier quoted context omitted.

Take the example at https://docs.rs/thiserror/latest/thiserror/ - Struct variant fields are public, limiting how you evolve the fields and types - Struct variants need non_exhaustive - It shows using `from` on an error. What happens if you want to include more context? Or change your impl which can change the source error type None of this is syntactically unique to errors. This becomes people's first thought of what…

Having private variants and fields would be useful, yeah. Std cheats a bit with its ErrorKind::Uncategorized unstable+hidden variant to have something unmatchable.

Probably because non-exhaustive enums didn't exist when it was written?

Re: Rust errors without dependencies

#74

My controversial take on Rust errors is to use anyhow everywhere until you have an immediate demand for explicit Enums. YANGNI The pros for using anyhow are big: Easily stack errors together - eg file.open().context(path) -, errors are easily kept up to date, and easy to find where they occur. An enum error is pleasing when you're finished, but cumbersome in practice. It's niche situation that you need to write a fun…

EEXIST. Lots of IO operations can fail because the destination already exists, and in quite a few cases, this isn’t really a failure so much as just a different outcome. In general, there is quite a bit of code that wants to branch on the specific error reported for an I/O operation.

Also, database errors. While the specific error may not be important, knowing whether an error means that a transaction definitely did not commit is valid, as is knowing whether retrying the transaction is likely to be useful. (The common case is retrying transactions that failed due to deadlock.)

Re: Rust errors without dependencies

#75

The fact that you either need a third party dependency or a large amount of boilerplate just to get decent error reporting, points to an issue in the language or std library design. I've started also dropping `thiserror` when building libraries, as I don't want upstream users of my libraries to incur this additional dependency, but it's a pain.

> points to an issue in the language or std library design. Rust has a too-small stdlib because they want to avoid a calcified stdlib like C++ and Python, which both have too-big stdlibs. This is a law of nature, your stdlib can either be too small or too big. It cannot be the right size. At least it isn't C.

I think you’re right with regards to the intention — but I’ve personally not experienced the case of an std lib being too big — good examples of “the right size” would be Go or Zig.

Re: Rust errors without dependencies

#76

Earlier quoted context omitted.

> At some point, you've got to overcome the fear of getting it wrong and ship something. Also Editions mean we can go back and fix things "for the future" in some cases if that's worth the price. For example I believe it's worth the price for the built-in ranges to become IntoIterator, indeed I hoped that could happen in the 2024 edition. It was, I think we'd both agree, worth it to fix arrays in 2021. This is one of…

Yeah, the edition thing is cool, but there's also a cost to it, which is that over time you accumulate more and more support code in the compiler for really ancient editions. I don't have a super good understanding of how it actually works in rustc. In C++ typically they would use name mangling tricks to try to keep new and old standard library symbols from clashing if they decided to break compatibility. Probably wi…

For the Range types, what would happen is (AIUI) something like this (example is for Range, but several other types are affected similarly):

1. The new ranges are stabilized, they become usable from stable Rust, as well as a core::ops::Range, the half-open range type in today's Rust, you'd be able to make a core::range::Range, a modern rendition of the same idea. The new type implements Copy and IntoInterator, and provides an iter() method to iterate over immutable references, all things the old type wasn't designed to accommodate.

2. A new Rust edition says that now A..B is syntax sugar for core::range::Range { start: A, end: B } rather than the same idea but for core::ops::Range. In your existing Rust your range notations are thus still core::ops::Range, but in new Rust projects written for a new edition they are core::range::Range with the nicer semantics.

So, there's no name mangling trick, the types continue to both exist, just when you write 0..4 meaning the integers 0, 1, 2 and 3, now you mean the modern type. You may need to write a little glue for older libraries to turn your modern types into Iterators they expect, but it's trivial and soon enough all modern Rust code would behave as though the ranges had always implemented Copy and IntoIterator just as today Rust code behaves as though the arrays had nice properties which in say 2018 Edition they did not.

In terms of mental models, an Edition can change what the concrete syntax of Rust means, so often the strategy is in two parts, 1: In ordinary releases ship an awkward way to express the new idea, this is 100% compatible with existing code, but has poor ergonomics - then 2: In an Edition change the language so that the new idea is easy to express

So you can see for Ranges, that first element would be stabilizing core::range::Range (and other similar types) then the edition would change the syntax.

Re: Rust errors without dependencies

#77
post #50
post #43

Earlier quoted context omitted.

Only if that different version is a dependency of a dependency. Your own will never change.

That does not make the problem go away: now you'll have both versions in your dependency graph - hence you may be vulnerable to both version's CVEs.

Any time you pull any code, be it `cargo add` or `apt install` or copy-pasting it in your own code, you become vulnerable to any issues present in that code. I'm unsure what your point is.

The claim is just that `cargo add crate` is functionally identical to downloading a C++ header and keeping it in the same version, since in both cases the dependency will be pinned to that fixed version.

Re: Rust errors without dependencies

#78
post #74

My controversial take on Rust errors is to use anyhow everywhere until you have an immediate demand for explicit Enums. YANGNI The pros for using anyhow are big: Easily stack errors together - eg file.open().context(path) -, errors are easily kept up to date, and easy to find where they occur. An enum error is pleasing when you're finished, but cumbersome in practice. It's niche situation that you need to write a fun…

EEXIST. Lots of IO operations can fail because the destination already exists, and in quite a few cases, this isn’t really a failure so much as just a different outcome. In general, there is quite a bit of code that wants to branch on the specific error reported for an I/O operation. Also, database errors. While the specific error may not be important, knowing whether an error means that a transaction definitely did…

Even then you can just err.downcast_ref::() though to get the underlying IOError, no?

Re: Rust errors without dependencies

#79
post #19

Earlier quoted context omitted.

If your error domain has only one useful category why not just create an error type with a useful message and be done with it. Why use anyhow at all? You are essentially saying the error domain is small so the work is tiny anyway. anyhow seems useful in the very top layers where you basically just want bubble the useful errors modeled elsewhere to a top layer that can appropriately handle them. I don't think a crate…

>I don't think a crate should abdicate from modeling the error domain any more than they should abdicate from modeling the other types. Yes, it's just harder. We usually have a pretty good idea what callers want from the happy path, but the range of things that callers may or may not want to do in case of an error is very broad.

This is an interesting perspective. I usually don't try to imagine how someone should handle the error. Instead I try to indicate the different types of errors that could occur and what type of information needs to be included to be useful to the caller. I can leave the question of what to do with that information to the caller since it's highly situational and the context necessary to make that decision lives outside of the code where I am modeling the error.

Re: Rust errors without dependencies

#80
post #19

Earlier quoted context omitted.

If your error domain has only one useful category why not just create an error type with a useful message and be done with it. Why use anyhow at all? You are essentially saying the error domain is small so the work is tiny anyway. anyhow seems useful in the very top layers where you basically just want bubble the useful errors modeled elsewhere to a top layer that can appropriately handle them. I don't think a crate…

I'm trying to describe a rather large spectrum of situations, and how most of them are favorable (or not unfavorable) to anyhow. I'm not saying the error domain is small per se. Instead, one argument I'm making: what you're describing about errors bubbling up to the top layer, is what happens with the overwhelming majority of errors in my experience. Whether the error space is large or small, just wait until you have…

    what you're describing about errors bubbling up to the top layer, is what happens with the overwhelming majority of errors in my experience.
I agree that this is what happens in practice for most code that I read and have to interact with. I think where I differ from you is that I don't think this is good and do not advise people to do this for their own code. I think it's a pervasive but bad practice in our line of work.
Post reply on HN