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

I’m also curious about the Kubernetes story—specifically how can one run this in Kubernetes?

Most components need to depend on an auth service, right? I don’t think that means it’s all necessarily one service (does all of Google Cloud Platform or AWS need to be a single service)?

That's immediately what I thought of. You'll never be able to satisfy this rule when every service has lines pointing to auth.

You'll probably also have lines pointing to your storage service or database even if the data is isolated between them. You could have them all be separate but that's a waste when you can leverage say a big ceph cluster.


The trick I've used is the N1 (gateway) service handles all AuthN and proxies that information to the upstream services to allow them to handle AuthZ. N+ services only accept requests signed by N1 - the original authentication info is removed.

I agree with this, and also I’m confused by the article’s argument—wouldn’t this apply equally to components within a monolith? Or is the idea that—within a monolith—all failures in any component can bring down the entire system anyway?

> wouldn’t this apply equally to components within a monolith?

It's a nearly universal rule you'll want on every kind of infrastructure and data organization.

You can get away for some time with making things linked by offline or pre-stored resources, but it's a recipe for an eventual disaster.


I like Go a lot, but I often wish we could be more explicit about where allocations are. It’s often important for writing performant code, but instead of having semantics we have to check against the stack analyzer which has poor ergonomics and may break at any time.

But yeah, to your point, returning a slice in a GC language is not some exotic thing.


I think I would like a “stackvar” declaration that works the same as “var” except my code won’t compile if escape analysis shows it would wind up on the heap. I say that knowing I’m not a language designer and have never written a compiler. This may be an obviously bad idea to somebody experienced in either of those.

I commented elsewhere on this post that I rarely have to think about stacks and heaps when writing Go, so maybe this isn’t my issue to care about either.


This could probably be implemented as an expensive comment-driven lint during compilation.

I don’t think it can be a true linter because it depends on the compiler. But it’s not a bad idea anyway

Escape analysis sends large allocation to the stack. The information is there.

Can you elaborate on the stack analyzer? All I could figure out was to see runtime.morestack calls that affected the runtime, but as far I remember the caller timings did exclude the cost. Having a clearer view of stack grow rates would be really great.

I’m not sure what you mean? Are you asking for information about what it is or how to use it?

I never heard of "stack analyzer" and didn't get meaningful results for it, do you mean escape analysis?

Sorry, yes, I meant “escape analyzer”. I’ve been jet lagged.

Ok no problem. Take a good sleep soon!

How's the build tooling these days? Last I tried, it used some jbuild/dune + makefiles thing that was really painful to get up and running. Also there were multiple standard libraries and (IIRC) async runtimes that wouldn't play nicely together. The syntax and custom operators was also a thing that I could not stop stubbing my toes on--while I previously thought syntax was a relatively unimportant concern, my experience with OCaml changed my mind. :)

Also, at least at the time, the community was really hostile, but that was true of C++, Ada, and Java communities as well well. But I think those guys have chilled out, so maybe OCaml has too?


I'm re-discovering OCaml these days after an OCaml burnout quite a few years ago, courtesy of my then employer, so I'm afraid I can't answer these questions reliably :/

So far, I like what I've seen.


    $ dune init project my-project
    $ dune build
That's it, now you have a compiling project and can start hacking.

Ocaml community is chill and helpful, and dune works great with really good compilation speeds.

Its a really nice language


> [Go] is like C in that you can fit the whole language in your head.

Go isn't like C in that you can actually fit the entire language in your head. Most of us who think we have fit C in our head will still stumble on endless cases where we didn't realize X was actually UB or whatever. I wonder how much C's reputation for simplicity is an artifact of its long proximity to C++?


30 years in C/C++ here.

Give an example of UB code that you have committed in real life, not from blogs. I am genuinely curious.


All the memory safety vulnerabilities, which are the majority of bugs in most C/C++ projects?

> Give an example of UB code that you have committed in real life

    struct foo {
        ...
        atomic_int v;
        ...
    };
    
    struct foo x;
    memset(&x, 0, sizeof(x));

I don't think it's UB if you init the struct before using it atomically from multiple threads.

I cautiously agree, with the caveat that while I thought I would really like Rust's error handling, it has been painful in practice. I'm sure I'm holding it wrong, but so far I have tried:

* thiserror: I spend ridiculous and unpredictable amounts of time debugging macro expansions

* manually implementing `Error`, `From`, etc traits: I spend ridiculous though predictable amounts of time implementing traits (maybe LLMs fix this?)

* anyhow: this gets things done, but I'm told not to expose these errors in my public API

Beyond these concerns, I also don't love enums for errors because it means adding any new error type will be a breaking change. I don't love the idea of committing to that, but maybe I'm overthinking?

And when I ask these questions to various Rust people, I often get conflicting answers and no one seems to be able to speak with the authority of canon on the subject. Maybe some of these questions have been answered in the Rust Book since I last read it?

By contrast, I just wrap Go errors with `fmt.Errorf("opening file `%s`: %w", filePath, err)` and handle any special error cases with `errors.As()` and similar and move on with life. It maybe doesn't feel _elegant_, but it lets me get stuff done.


FWIW `fmt.Errorf("opening file %s: %w", filePath, err)` is pretty much equivalent to calling `err.with_context(|| format!("opening file {}", path))?` with anyhow.

What `thiserror` or manually implementing `Error` buys you is the ability to actually do something about higher-level errors. In Rust design, not doing so in a public facing API is indeed considered bad practice. In Go, nobody seems to care about that, which of course makes code easier to write, but catching errors quickly becomes stringly typed. Yes, it's possible to do it correctly in Go, but it's ridiculously complicated, and I don't think I've ever seen any third-party library do it correctly.

That being said, I agree that manually implementing `Error` in Rust is way too time-consuming. There's also the added complexity of having to use a third-party crate to do what feels like basic functionality of error-handling. I haven't encountered problems with `thiserror` yet.

> Beyond these concerns, I also don't love enums for errors because it means adding any new error type will be a breaking change. I don't love the idea of committing to that, but maybe I'm overthinking?

If you wish to make sure it's not a breaking change, mark your enum as `#[non_exhaustive]`. Not terribly elegant, but that's exactly what this is for.

Hope it helped a bit :)


> In Rust design, not doing so in a public facing API is indeed considered bad practice. In Go, nobody seems to care about that, which of course makes code easier to write, but catching errors quickly becomes stringly typed. Yes, it's possible to do it correctly in Go, but it's ridiculously complicated, and I don't think I've ever seen any third-party library do it correctly.

Yea this is exactly what I'm talking about. It's doable in golang, but it's a little bit of an obfuscated pain, few people do it, and it's easy to mess up.

And yes on the flip side it's annoying to exhaustively check all types of errors, but a lot of the times that matters. Or at least you need an explicit categorization that translates errors from some dep into retryable vs not, SLO burning vs not, surfaced to the user vs not, etc. In golang the tendency is to just slap a "if err != nil { return nil, fmt.Errorf" forward in there. Maybe someone thinks to check for certain cases of upstream error, but it's reaaaallly easy to forget one or two.


Yeah, there's a mismatch in vocabulary between Go and Rust developers.

In Go, `if err != nil { return nil, fmt.Errorf(...) }` is considered handling an error.

In Rust, the equivalent `.context(...)?` is considered passing an error. Handling it is about finding out what happened and doing something about it.


> In Go, nobody seems to care about that, which of course makes code easier to write, but catching errors quickly becomes stringly typed.

In Go we just use errors.Is() or errors.As() to check for specific error values or types (respectively). It’s not stringly typed.

> If you wish to make sure it's not a breaking change, mark your enum as `#[non_exhaustive]`. Not terribly elegant, but that's exactly what this is for.

That makes sense. I think the main grievance with Rust’s error handling is that, while I’m sure there is the possibility to use anyhow, thiserror, non_exhaustive, etc in various combinations to build an overall elegant error handling system, that system isn’t (last I checked) canon, and different people give different, sometimes contradictory advice.


> In Go we just use errors.Is() or errors.As() to check for specific error values or types (respectively). It’s not stringly typed.

errors.Is() works only if the error is a singleton.

errors.As() works only if the developer has defined their own error implementing both `Error() string` (which is part of the `error` interface) and either `Unwrap() error` or `Unwrap() error[]` (neither of which is part of the `error` interface). Implementing `Unwrap()` is annoying and not automatizable, to the point that I've never seen any third-party library doing it correctly.

So, in my experience, very quickly, to catch a specific error, you end up calling `Error()` and comparing strings. In fact, if my memory serves, that's exactly what `assert` does.

> I think the main grievance with Rust’s error handling is that, while I’m sure there is the possibility to use anyhow, thiserror, non_exhaustive, etc in various combinations to build an overall elegant error handling system, that system isn’t (last I checked) canon, and different people give different, sometimes contradictory advice.

Yeah, this is absolutely a problem in Rust. I _think_ it's moving slowly in the right direction, but I'm not holding my breath.


> Beyond these concerns, I also don't love enums for errors because it means adding any new error type will be a breaking change. I don't love the idea of committing to that, but maybe I'm overthinking?

Is it a new error condition that downstream consumers want to know about so they can have different logic? Add the enum variant. The entire point of this pattern is to do what typed exceptions in Java were supposed to do, give consuming code the ability to reason about what errors to expect, and handle them appropriately if possible.

If your consumer can't be reasonably expected to recover? Use a generic failure variant, bonus points if you stuff the inner error in and implement std::Error so consumers can get the underlying error by calling .source() for debugging at least.

> By contrast, I just wrap Go errors with `fmt.Errorf("opening file `%s`: %w", filePath, err)` and handle any special error cases with `errors.As()` and similar and move on with life. It maybe doesn't feel _elegant_, but it lets me get stuff done.

Nothing stopping you from doing the same in Rust, just add a match arm with a wildcard pattern (_) to handle everything but your special cases.

In fact, if you suspect you are likely to add additional error variants, the `#[non_exhaustive]` attribute exists explicitly to handle this. It will force consumers to provide a match arm with a wildcard pattern to prevent additions to the enum from causing API incompatibility. This does come with some other limitations, so RTFM on those, but it does allow you to add new variants to an Error enum without requiring a major semver bump.


I will at least remark that adding a new error to an enum is not a breaking change if they are marked #[non_exhaustive]. The compiler then guarantees that all match statements on the enum contain a generic case.

However, I wouldn't recommend it. Breakage over errors is not necessarily a bad thing. If you need to change the API for your errors, and downstreams are required to have generic cases, they will be forced to silently accept new error types without at least checking what those new error types are for. This is disadvantageous in a number of significant cases.


Indeed, there's almost always a solution to "inergonomics" in Rust, but most are there to provide a guarantee or express an assumption to increase the chance that your code will do what's intended. While that safety can feel a bit exaggerated even for some large systems projects, for a lot of things Rust is just not the right tool if you don't need the guarantees.

On that topic, I've looked some at building games in Rust but I'm thinking it mostly looks like you're creating problems for yourself? Using it for implementing performant backend algorithms and containerised logic could be nice though.


If you're willing to do what you're saying in Go, exposing the errors from anyhow would basically be the same thing. The only difference is that Rust also gives all those other options you mention. The point about other people saying not to do it doesn't really seem like it's something you need to be super concerned with; for all we know, people might tell you the same thing about Go if it had the ability for similar APIs, but it doesn't

> I also don't love enums for errors because it means adding any new error type will be a breaking change

You can annotate your error enum with #[non_exhaustive], then it will not be a breaking change if you add a new variant. Effectively, you enforce that anybody doing a match on the enum must implement the "default" case, i.e. that nothing matches.


You have to chill with rust. Just anyhow macro wrap your errors and just log them out. If you have a specific use case that relies on using that specific error just use that at the parent stack.

I personally like the flexibility it provides. You can go from very granular with an error type per function and an enum variant per error case, or very coarse with an error type for a whole module that holds a string. Use thiserror to make error types in libraries, and anyhow in programs to handle them.

Yeah, a couple years ago I built a system that undergirded what was at the time a new product but which now generates significant revenue for the company. That system is shockingly reliable to the extent that few at the company know it exists and those who do take its reliability for granted. It's not involved in any cost or reliability fires, so people never really have to think about how impressive this little piece of software really is--the things they don't need to worry about because this software is chugging along, doing its job, silently recovering from connectivity issues, database maintenance, etc without any real issue or maintenance.

It's a little bit of a tragic irony that the better a job you do, the less likely it is to be noticed. (:


Note the projects that use that software, also note metrics like API calls received, failure recoveries, uptime, etc and put that in a promo packet

Thanks, I genuinely appreciate the advice!

May be you need to have "scheduled downtime" when your undergirding system is down for "maintenance" and they will notice! [Half joking... Probably not possible but better to have scheduled maintenance than have to do firefighting under extreme time pressure]

I had a coworker legitimately put wait statements in his code so that later he could remove them and report the optimizations. I approved a few of them

Gather metrics and regularly report them.

> they abandoned POSIX compatibility, built a massively over-complicated product

This is a wild sentence--how can you criticize them for abandoning POSIX support __and__ building a massively over-complicated product? Making a reliable POSIX system is inherently very complex.


I think the criticism (just interpreting the post, don’t know anything about the technical situation) is that the complication is not necessary/worthwhile.

POSIX can be complicated, but it puts you in a nice ecosystem, so for some use-cases complex POSIX support is not over complicated. It is just… appropriately complicated.


Sure, but then you can make that argument about any of the features in Minio, in which case the parent's argument about Minio as a whole being overcomplicated is invalidated. Probably the more sensible way to look at things is "value / complexity" or "bang for buck", but even there I think POSIX loses since it's relatively little value for a relatively large amount of complexity.

Yeah. I don’t actually know if they are right or wrong, it depends on the ecosystem the project wants to hook in to, right? I just want to reduce it from “wild” to “debatable,” haha.

What would go in to POSIX compatibility for a product like this which would make it complicated? Because the kind of stuff that stands out to me is the use of Linux specific syscalls like epoll/io_uring vs trad POSIX stuff like poll. That doesn't seem too complicated?

It took me an embarrassingly long time to realize this wasn’t about exercising.


I think it would be better if it were about exercising; it's more useful advise that way.

So, all it takes is for you to work out: You may not get into that grad school / that job, but you'll be healthier and you'll feel less depressed. In fact, even if you're in grad school, it's pretty good advice for coping avoiding burnout, coping with impostor syndrome etc.


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

Search: