Hacker Newsnew | past | comments | ask | show | jobs | submit | jgilias's commentslogin


Thank you for link.

As an aside there: the blog post briefly talks about birds. It turns out that membrane wings are much easier to evolve than feathered wings. There have been lots of membrane winged creatures (including "birds" with membrane wings in the Jurassic) but not nearly as many appearances of feathered wings.

https://www.youtube.com/watch?v=HxA38gH8Gj4


Is it really though? What’s the point of having an error type per function? As the user of std::io, I don’t particularly care if file couldn’t be read in function foo, bar, or baz, I just care that the file couldn’t be read.

An error type per function doubles as documentation. If you treat all errors as the same it doesn't matter, but if you have to handle some, then you really care about what actual errors a function can return.

Ok, that’s a valid point! Though there’s a trade-off there, right? If both bar and baz can not find a file, they’re both going to return their own FileNotFound error type. And then, if you care about handling files not being found somewhere up the stack, don’t you now have to care about two error types that both represent the same failure scenario?

A framing about the problem I don't often see is: when do you want to throw away information about an error case? Losing that information is sometimes the right thing to do (as you said, maybe you don't care about which file isn't found by a function, so you only use one error to represent the two times it can happen).

Maybe it would make sense to consider the API a function is presenting when making errors for it; if an error is related to an implementation detail, maybe it doesn't belong in the public API. If an error does relate to the public function's purpose (FileNotFound for a function that reads config), then it has a place there.


I agree, it's tough to know when the right time to toss information away is, to simplify things for the caller.

When in doubt, I tend to prefer "wrapper style" errors for libraries, so the caller can match at whatever level of specificity they care about. As a toy example:

    match read_config() {
      Err(ConfigError(FileNotFound) => println!("No configuration file found. Supported config directories are: {}", get_config_dirs()),
      Err(ConfigError(IOError(filename, msg))) => println!("Access error for config file {}: {}", filename, msg),

      Err(ConfigError(ParseError(e))) => println!("Parse error: {:?}", e),

      Err(ConfigError(e)) => println!("configuration error: {:?}", e),
      Ok(config) => {...},
    }
The calling application could start with just the generic error message at the end, and over time decide to add more useful behavior for some specific cases.

Of course, then the problem becomes "what are the useful or natural groupings" for the error messages.


I did this with a moderate sized cli tool; it was really good to be able to see effectively every error state you could have at the top level of the program.

Something I didn't find an ergonomic solution to though was pulling out common errors; I wanted to say, pull out any std::io errors that occurred at the top level, but all I found was to match and expand every error case to pull them out.

I considered maybe a derive/trait based approach would work, but it was too big a hammer for that project.


I don't know Rust, but I really liked Boost.System's approach[1], which was included in C++11, and have used that scheme in other languages.

The point there is the error is not just an error in isolation, but it has an attached error category as well. And the error categories can compare errors from other categories for equivalence.

So for example, say you have an error which contains (status_404, http_result_category), then you can compare that instance with (no_such_file_or_directory, generic_category), and because http_result_category knows about generic_category, it can handle the comparison and say that these are equivalent[2].

This allows you to preserve the quite detailed errors while also using them to handle generic error conditions further up.

That said, doing this for every function... sounds tedious, so perhaps not worth it.

[1]: https://www.boost.org/doc/libs/1_88_0/libs/system/doc/html/s...

[2]: https://www.boost.org/doc/libs/1_88_0/libs/system/doc/html/s...


To me this is backwards. I don't think there is a common need to handle a generic file not found error. Let's say the user tries to open an image file. The image file exists, but when decoding the image file you need to open some config file which happens to be missing. That needs entirely different handling than if the image file itself was missing.

Though, I suppose with something broader like IOException the situation is different.

   try {
       open file
       read some bytes
       read some more bytes
   }
makes sense, as they all relate to the same underlying resource being in a good state or not.

if you have multiple files that are read in a function, and they might lead to different error handling... then sometimes yeah, perhaps they should be different types so you are guaranteed to know that this is a possibility, and can choose to do something specific when X happens. or to be informed if the library adds another file to the mix.

it isn't always the case, of course, but it also isn't always NOT the case.


The approach with the `error_set` crate lets you unify the error types how you like, in a declarative sort of way. If you want to always treat FileNotFound the same way up top, that's totally doable. If you want them wrapped with types/enums to reflect the stack they came up, that also works.

The main page of the doc explains pretty well how things can be wrapped up: https://crates.io/crates/error_set You define the data structure, and it'll take care of generating the From impls for you, so you can generally just do `f()?` when you don't care about the specifics, and `match f1()` and destructure when you do.


Yeah… Please no.

I’m getting a bit of a macro fatigue in Rust. In my humble opinion the less “magic” you use in the codebase, the better. Error enums are fine. You can make them as fine-grained as makes sense in your codebase, and they end up representing a kind of an error tree. I much prefer this easy to grok way to what’s described in the article. I mean, there’s enough things to think about in the codebase, I don’t want to spend mental energy on thinking about a fancy way to represent errors.


Yes. Macros are a hammer, but not everything is a nail.

Declarative macros (macro_rules) should be used to straightforwardly reduce repetitive, boilerplate code generation and making complex, messy things simpler.

Procedural macros (proc_macro) allow creating arbitrary, "unhygienic" code that declarative macros forbid and also custom derive macros and such.

But it all breaks down when use of a library depends too much on magic code generation that cannot be inspected. And now we're back to dynamic language (Ruby/Python/JS) land with opaque, tinkering-hostile codebases that have baked-in complexity and side-effects.

Use magic where appropriate, but not too much of it, is often the balance that's needed.


Rust is trying very hard to compete with C++. That includes giving everyone a hammer so that every problem can be a thumb.

> Yes. Macros are a hammer, but not everything is a nail.

Overuse of macros is a symptom of missing language capabilities.

My biggest disappointment in Rust (and probably my least popular opinion) is how Rust botched error handling. I think non-local flow control (i.e. exceptions) with automated causal chaining (like Python) is a good language design point and I think Rust departed from this good design point prematurely in a way that's damaged the language in unfixable ways.

IOW, Rust should have had _only_ panics, and panic objects should have had rich contextual information, just like Java and Python. There should also have been an enforced "does not panic" annotation like noexcept in C++. And Drop implementations should not be allowed to panic. Ever.

God, I hope at least yeet gets in.


> Rust should have had _only_ panics, and panic objects should have had rich contextual information, just like Java and Python.

It could have gone that way, but that would have “fattened” the runtime and overhead of many operations, making rust unsuitable for some low-overhead-needed contexts that it chose to target as use-cases. More directly: debug and stack info being tracked on each frame has a cost (as it does in Java and many others). So does reassembling that info by taking out locks and probing around the stack to reassemble a stack trace (C++). Whether you agree with Rust’s decision to try to serve those low-overhead niches or not, that (as I understand it) is a big part of the reason for why errors work the way they do.

> There should also have been an enforced "does not panic" annotation like noexcept in C++. And Drop implementations should not be allowed to panic.

I sometimes think that I’d really love “nopanic”. Then I consider everything that could panic (e.g. allocating) and I like it less. I think that heavy use of such a feature would lead to people just giving up and calling abort() in library code in order to be nopanic-compatible, which is an objectively worse outcome than what we have today.


> debug and stack info being tracked on each frame has a cost

So add an option not to collect the debugging information. The core exception mechanism remains.

> Whether you agree with Rust’s decision to try to serve those low-overhead niches or no

It's not a matter of Rust choosing to serve those niches or not. It's the language designers not adequately considering ways to have exceptions and serve these niches. There's no contradiction: it's just when Rust was being designed, it was _fashionable_ to eschew exceptions.

> Then I consider everything that could panic (e.g. allocating) and I like it less. I think that heavy use of such a feature would lead to people just giving up and calling abort() in library code in order to be nopanic-compatible,

Huh? We don't see people write "noexcept" everywhere in C++ to be noexcept-compatible or something. Nopanic is for cleanup code or other code that needs to be infallible. Why would most code need to be infallible? I mean, panic in Drop is already very bad, so Rust people know how to write infallible code. The no-failure property deserves a syntactic marker.


In anything performance sensitive like OSes or games, C++ is compiled without exceptions. Unwinding is simply unacceptable overhead in the general case.

Rust got errors right, with the possible exception of stdlib Error types.


Table based unwinding is just one implementation choice. You can make other choices, some of which compile to code similar to error values. See Herb Sutter's deterministic exception proposal.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p07...

Your post is a fantastic example of the problem I'm talking about: you're conflating a concept with one implementation of the concept and throwing away the whole concept.

Language design and implementation are different things, and as an industry, we used to understand that.


> Overuse of macros is a symptom of missing language capabilities.

Agree.

> I think non-local flow control (i.e. exceptions) with automated causal chaining (like Python) is a good language design point

Stronly disagree: https://home.expurple.me/posts/rust-solves-the-issues-with-e...

> There should also have been an enforced "does not panic" annotation like noexcept in C++.

noexcept DOES NOT mean that the function can't throw an exception! It just means that, when it does, it aborts the program instead of unwinding into the calling function. Quoting cppreference [1]:

> Non-throwing functions are permitted to call potentially-throwing functions. Whenever an exception is thrown and the search for a handler encounters the outermost block of a non-throwing function, the function std::terminate is called

> And Drop implementations should not be allowed to panic. Ever.

Should `panic=abort` panics be allowed in Drop? They are effectively the same as std::process::exit. Do you want to mark and ban that too?

[1]: https://en.cppreference.com/w/cpp/language/noexcept_spec.htm...


> Stronly disagree: https://home.expurple.me/posts/rust-solves-the-issues-with-e...

50% at least of these tiresome "here's why exceptions suck" articles begin by talking about how "try" is un-ergonomic. The people writing these things misunderstand exceptions, probably never having actually used them in a real program. These writers think of exceptions as verbose error codes, and think (or pretend to think) that using exceptions means writing "try" everywhere. That's a strawman. Exceptional programs don't need error handling logic everywhere.

The article's author even admits at the end that Rust's error handling is garbage and forces programmers to do manually ("best practices around logging" --> waste your brain doing a computer's work) what languages with decent exception systems do for you.

> noexcept DOES NOT mean that the function can't throw an exception! It just means that, when it does, it aborts the program instead of unwinding into the calling function

Well, yeah. It means the rest of the program can't observe the function marked noexcept throwing. No... except. Noexcept. See how that works?

> Should `panic=abort` panics be allowed in Drop? They are effectively the same as std::process::exit. Do you want to mark and ban that too?

Aborting in response to logic errors is the right thing to do, even in destructors.


I'm the author of this article, btw :)

> 50% at least of these tiresome "here's why exceptions suck" articles begin by talking about how "try" is un-ergonomic.

Idk about the other articles, but mine doesn't begin with that. The first argument in the article is this: "exceptions introduce a special try-catch flow which is separate from normal returns and assignments" (when there's no entrinsic reason why errors shouldn't be returned and assigned normally). The first mentioned implication of that is ergonomics, but I immediately follow up with the implications regarding reliability and code clarity. See the "Can you guess why I used an intermediate variable" toggle. Later, I also bring up a separate disadvantage of having to manually document thrown unchecked exceptions.

> The people writing these things misunderstand exceptions, probably never having actually used them in a real program.

I've supported C++ and Python applications in production.

> pretend to think that using exceptions means writing "try" everywhere.

Nope: "[propagation] is a very common error-handling pattern, and I get why people want to automate it".

> That's a strawman. Exceptional programs don't need error handling logic everywhere.

Where does the article say otherwise? You're the one pulling a strawman here.

> The article's author even admits at the end that Rust's error handling is garbage

You're free to make that conclusion. In the end, the tradeoff is subjective. But it's not the conclusion that I make in the article.

> forces programmers to do manually ("best practices around logging" --> waste your brain doing a computer's work)

That's true. But languages with unchecked exceptions force you to manually check the documentation of every method you call, in order to see whether it can throw any exceptions that you're interested in catching. And that documentation can simply be incorrect and let you down. And the set of exceptions can silently change in the next version of the library (the compiler won't tell you). And refactoring your code can silently break your error handling (the compiler won't tell you). And manually verifying the refactoring is really hard because you can't use local reasoning (`catch` is non-local and "jumps" all across the layers of your app).

It's a tradeoff.


I almost never see people "handle" errors and actually add value, though... it feels a lot like how people sprinkle timeouts throughout their logic that just serve to make the system less stable. Almost all of the time--like, seriously: almost all of the time, not 99% of the time, or 99.9% of the time, but almost every single time--you call a function, you shouldn't care what errors it can raise, as that's not your problem. In a scant handful of places throughout your entire project--in the context of a web site backend, this often won't even be in your code at all: it will be taken care of inside of the router--you will catch errors, report them, and provide a way for the operation to retry somehow; but, if you care why the error happened, either the API was designed wrong (which sometimes happens) or you are using it wrong. You have to already misunderstand this aspect of error design in order to even contemplate the existence of a language that forces people to deal with local error handling.

> almost all of the time, not 99% of the time, or 99.9% of the time, but almost every single time--you call a function, you shouldn't care what errors it can raise

Sure, that's often the case. That's why dynamically-typed anyhow::Error is so popular.

But I really care whether a function can raise at all. This affects the control flow in my program and composability of things like `.map()`. `Result` is so good because it makes "raising" functions just as composable as "normal" functions. When you `.map()`, you need to make a decision whether you want it to stop on the first error or keep going and return you Results with all individual errors. Rust makes it very easy and explicit, and allows to reuse the same `.map()` abstraction for both cases.

> a language that forces people to deal with local error handling.

It does that for the reason above: explicit control flow. See the "Can you guess why I used an intermediate variable" toggle in the article.

It doesn't mean that you have to do full "local error handling" on every level. 99% of the time, `?` operator is used. Because, as you've said, 99% of the time you just want to propagate an error. That's understood in the Rust community and the language supports it well.

When you need to wrap the error for some reason, `?` can even do that automatically for you. That's what makes anyhow::Error so seamless and sweet. It automatically wraps all concrete library errors and you no longer need to deal with their types.

Basically, `Result<T, anyhow::Error>` is `throws Expection`. But, like, ergonomic, composable and actually useful.


Agreed about magic.

Please correct me if I’m misunderstanding this, but something that surprised me about Rust was how there wasn’t a guaranteed “paper trail” for symbols found in a file. Like in TypeScript or Python, if I see “Foo” I should 100% expect to see “Foo” either defined or imported in that specific file. So I can always just “walk the paper trail” to understand where something comes from.

Or I think there was also a concept of a preamble import? Where just by importing it, built-ins and/or other things would gain additional associated functions or whatnot.

In general I just really don’t like the “magic” of things being within scope or added to other things in a manner that it’s not obvious.

(I’d love to learn that I’m just doing it wrong and none of this is actually how it works in Rust)


It’s a bit confusing sometimes with macros that create types that don’t seem to exist. But usually when I work with code I use an IDE anyway and “go to definition” will bring me to where it’s defined, even when it’s via a macro.

Still generally prefer the plain non-macro declarations for structs and enums though because I can easily read them at a glance, unlike when “go to definition” brings me to some macro thing.


Doesn't something like the following break the trail in pretty much all languages?

    from package import *

Yup. And I’ve not seen that used in forever and it’s often considered a linting error because it is so nasty.

So maybe what I’m remembering about Rust was just seeing a possible but bad convention that’s not really used much.


You can import everything from a module with a *, but most people seem to prefer to import things explicitly. But, yes, you can generally figure out easily where things are coming from!

Both sides have been a pain for me. Either I’m debugging macro errors or else I’m writing boilerplate trait impls all day… It feels like a lose/lose. I have yet to find a programming language that does errors well. :/

Annotations were once condemned as 'magic' for doing things at runtime. Now it's apparently fine to use a language where most non-trivial code depends on macros. Tools that rewrite your code at compile time, often invisibly. But hey, it's not magic if it's your magic, right?

Don’t forget some type of strength training. There’s plenty of research that it reduces all cause mortality. Likely by pushing back the time you become frail.


This might get turned on its head in a couple years. Some new research just came out on combining incretin based therapies (ie semaglutide) with myostatin blockers (in this case trevogrumab and garetosmab) and the monkey lost a crap load of fat while also putting on muscle mass. It's a hell of a time to be a monkey, hopefully it translates into a hell of a time to be a human in a few years. Also, I would like some samples of whatever substance the guys naming these drugs are consuming. Whatever it is, they are wasting it on naming drugs when they should be using it to write science fiction.


Obviously the inventors were called Trevor and Garett


Link to the research?


Absolutely. I saw this with aging loved ones. The most “durable” ones fared better as they aged and had health issues.

As you age, each acute medical event has a real impact and recovery is slow and limited. You have to be at an high baseline to crawl back up.

My dad had a stroke that really affected him badly, but he recovered a lot and worked hard. It was all set back by a cold and a uti that resulted in a hospitalization. That basically did him in. Everything you can do to make sure that you can stand up and get around as long as possible means that you’ll be able to live a longer fulfilling life.


My grandfather was hit by a car at 80 and only recovered because he has the constitution of a tank.

Even afterwards he fights daily to do everything himself.


I should, but I don't. I do hike four hours a week and swim for a half hour every day very early in the morning. Thanks for the reminder, you are 100% correct. I am in my mid 70s, so I think I only need resistance training about twice a week.


You need it every day at that age to preserve bone density.


Here's an exercise anyone can do almost everyday that will increase bone density. I do it in the shower or wherever there's a solid safe tile floor:

Start with both knees and hips bent (like a football linebacker just before the snap), legs apart and arms bent at the side to maintain balance. Set one foot slightly ahead of the other. Now, using primarily the heels of your feet, jump up slightly(preferably only a few inches) and slam both heels back down, while switching which foot is forward. [Don't jump so much that you straighten your legs; instead keep knees bent at all times]. You're hopping in place and striking your heels on the ground, with the right foot forward first and then with the left foot forward, back and forth. Make sure the heel hits the tile and bears the brunt of the force (rather than the toes). Do 20 of these hops each time you're in the shower.

Slamming the heel of the foot into the floor vibrates the large bones of the legs. These vibrations will strengthen (all of) your bone over time.


Is there a name for this particular exercise?


Well, I call it the "longer balls" exercise b/c, each time your heels hit the floor, unless you grab your nut sack, you will feel the impact!8-))

So, to correct the instructions:

...stand legs apart and arms bent at the side to maintain balance, nutsack in one hand if male, ..


Citation?


Need what exactly? Resistance training or strength training? Or both?


actually you need it 3 times a day


Well, yes. But nobody is running the entire industry. You’re running a company that has competitors willing to eat your lunch.


If those are remote positions, why do you care about any of this at all? Make it clear that the applicant needs to handle taxation in their home country themselves, and that’s that. I know many software engineers working remotely for companies outside their home country. One would typically set up an LLC or equivalent, and be employed by that.

Any options agreement would be with the private individual to not mess up the cap table.


I believe this depends on the individual. For me, yeah, I am. But I do have colleagues who wouldn’t be.


Well, if it comes to that. German is not _really_ a single language. It’s a dialect continuum consisting of sometimes barely mutually intelligible variants. And yes, if you continue following that continuum, you get to the languages you mention.

A language is a dialect with an army and a fleet. As they used to say.


'A language is a dialect with an army and a fleet ' --> I hadn't heard this before, love it! For the curious: https://en.wikipedia.org/wiki/A_language_is_a_dialect_with_a...


A bit of relevant context: the quote is from Yiddish, which is primarily Germanic with significant admixture from Hebrew, Aramaic, and Slavic languages.

(One of my current favorite party tricks is speaking Yiddish to German speakers, and cranking up the other aspects to see where the intelligibility breaks down.)


I took a trip to Germany with my Dad, who grew up with Yiddish-speaking parents, and it was amazing to watch people's eyeballs pop out as they began to understand him and then realize what they were hearing.


zaftig.


זאַפטיג איז יא אַ גוט װאָרט :-)


> A language is a dialect with an army and a fleet. As they used to say.

I usually say "A dialect is a language that lost a war", but this one might be better :)


And the continuum has two big groups: High and Low German (High and Low here being z-coordinates, High German dialects because they come from the more mountainous Southern areas and Low German from the lower-lying Northern parts). Modern day Standard German is a High German variant, whereas Dutch (and thus Afrikaans) are Low German.


Sorry, got nerd sniped: Isn't it usually the y coordinate that stands for the vertical axis? At least that's how I know 3D coordinate systems, with the z axis either increasing towards or away from the center, left handed vs right handed coordinate systems.


In GIS parlance, elevation is typically z coordinate. Which of x and y correspond to east-west and north-south varies from coordinate system to coordinate system, and is a bountiful source of stupid bugs (at least for me). But yeah, in 3D graphics z is usually distance from the camera I think.



I see, fascinatingly inconsistent :D I somehow have the urge to popularise a coordinate system where the x axis represents elevation now.


Towards or away from the center is the height, no? As in, you’re the bird at height Z observing the x-y plane under you, and the Z axis goes into you. Or away from you if you happen to be a mole under ground.


Then just to muddy things you also have the Low Lands, which aren't in Germany, but they do speak a Germanic language there. ;)


Curious, what are the practical concerns? The place looks fantastic to me!

I really miss more bold architectural and city planning experiments. Like, I get it, if it’s a flop, it’s a pretty expensive one. But still, it feels like the design-space there is just really under-explored.

Maybe there’s some AI-driven simulation way to explore the design-space and arrive at viable solutions before committing too much funds.

One can dream.


Like OJFord mentions, it is a bit far from amenities despite the straight line distance to them not being great because it's built on an island in the river and you have to cross the bridge to get to the downtown area.

But more importantly for me, my usual life is not in Montreal. I love Montreal but moving there would require quite a few sacrifices in personal relationships that I don't feel like making. And government services in Quebec are also worse than in Ontario (where I am now).


Not GP but the linked Wikipedia page says though there's a private shuttle it's difficult to get into town on foot, so I'd guess it's concern that it'd be annoyingly secluded/disconnected at times.


Came to see if someone’s brain parsed it that way too!


Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: