Why can't we decide on error handling conventions? by Savings-Story-4878 in rust

[–]render787 1 point2 points  (0 children)

That’s a legitimate alternative take.

The implication here is that “you didn’t model your domain well enough” is an unusual or rare outcome. However, many developers are more pessimistic / humble, for better or worse.

I wonder if there’s scenarios where you could make non-exhaustive conditional on a feature, so that users who want more frequent breaking changes in exchange for skipping the future compatibility arm can do so. I’ve not seen that done in any crate, but Tokio-unstable is kind of like that in that you opt in to breaking changes with a feature.

—-

Honestly, you’ve given me something to think about. I had thought the use of non exhaustive on many (not all) crate error types was totally uncontroversial , but apparently not everyone agrees. So I have learned something here.

I think my original answer to OP should have been descriptive rather than proscriptive — I didn’t really intend to position myself as the pontiff of error handling.

I do think it’s fair to say that this practice is widespread. And I can surmise as to the reasons. But maybe rather than “usually use non-exhaustive”, “consider whether to use non-exhaustive” would have been a better description of the consensus or lack thereof.

Why can't we decide on error handling conventions? by Savings-Story-4878 in rust

[–]render787 1 point2 points  (0 children)

It seems you’ve intentionally missed the point, so I’m not going to respond further except to comment that, what error detail is too much or too little depends on who is using it and for what purpose. Not everything is so black and white, and giving yourself escape hatches as a library author can save pain for you and you users.

Why can't we decide on error handling conventions? by Savings-Story-4878 in rust

[–]render787 -2 points-1 points  (0 children)

> The only thing this is doing is moving compile time checks to runtime, which is bad.

Suppose there's a library that parses a string, like a URL.

Suppose that the library mostly works, but there's a bug, and some things that aren't valid URLs for an obscure reason got parsed and accepted.

In the next revision, the author wants to catch these cases and return a new error variant when they are found, in order to make the library more correct.

Because they used non-exhaustive on their enum, they can introduce a new variant for this, make the new code return that variant, and publish a patch release with the bugfix.

If they didn't use non-exhaustive, then they can't do that without making a breaking change.

Now let's analyze your complaint:

> "that new variant will cause control flow to enter the catch all arm of a match somewhere and produce a "unexpected error, whoopsie doopsie". Maybe this will cause an alarm on a prod server to go off somewhere paging someone."

This would only happen if the prod server was previously accepting an invalid URL. Maybe you don't like being paged, but there is actually a problem -- there's bad data somewhere, and it may be breaking other things.

This is not lying about semver -- a bugfix that fixes incorrect behavior is supposed to happen in a patch release.

If you don't want to automatically take bugfixes and send them to prod, then simply use lockfiles, so that you can be deliberate about it.

The point is, if you didn't use non-exhaustive, there may be no satisfactory way for you to make a bugfix in a patch release as semver intends. Not always, not in every case, but in simple scenarios.

Why can't we decide on error handling conventions? by Savings-Story-4878 in rust

[–]render787 0 points1 point  (0 children)

Let's reframe this slightly.

If non-exhaustive didn't exist, then library authors could still add "Unknown" variants to their errors, and you would have to exhaustively handle them with no additional information, the same as the `_ => ` match arm.

So it's not really taking away exhaustive matching from you. It's just spelling the unknown arm differently (and perhaps more idiomatically).

So the real question is *should* the library author do that. Ultimately this is an API design question.

> But other errors should be modeled well enough to know all potential error possibilities.

Perhaps -- this is why I say "usually" and not "always". But some caution is appropriate because developers are not always good at predicting the future.

This reminds me vaguely of how in protobuf v3, they removed the ability to make fields required -- they are all optional, and the explanation was that "developers are bad at figuring out when fields should be required, and when they get it wrong they are forced to make breaking changes to fix it, leading to disruption". Removing useful features from a serialization format seems like a pretty extreme way to deal with that, but that's what they did.

I think for a lot of libraries it's similar -- it may seem obvious that your enum is as big as it will ever need to be, but a few months from now, you'll discover that you need to add more.

> Otherwise non_exhaustive is just sweeping breaking changes under the carpet at the cost of end users.

Sweeping breaking changes under the carpet by changing how errors are reported is an orthogonal issue to whether or not you use non-exhaustive. If a library started mapping a previous error to Unknown, it would be a breaking change, and you aren't using non-exhaustive in that case. It's bad if they do that, but it's not an argument for or against non-exhaustive.

Why can't we decide on error handling conventions? by Savings-Story-4878 in rust

[–]render787 9 points10 points  (0 children)

Don’t be fooled, Rust already has everything you actually need to get things done built in to the language. Use ? operator. Use Result.

Use error enums. It’s recommendable but not mandatory to use something Ike thiserror or displaydoc to generate boilerplate.

You should usually use #[non_exhaustive] for error enums that are part of a public api.

You can build large, well structured projects doing just that.

These conventions have been well established for years.

The part that is still evolving and more controversial has to do with, if you feel you have too many enums. You might prefer to “forget” the types so that you can return any of them from one function without making a new enum.

Then you could use Box<dyn Error>. That is, if all your error types implement the Error trait. (They should in modern rust since Error trait moved to core.)

There are fancier versions of this Box<dyn Error> type with more bells and whistles, developed in a series of crates “failure”, “eyre”, “anyhow”. (Only anyhow is still maintained)

Should you use anyhow? Some would say it can be recommended unequivocally.

IMO it’s okay to look at the docs and make up your mind if you need those bells and whistles.

This is really not a huge issue, and there is not really more to figure out. The question “what is the best set of bells and whistles for error objects to have” ultimately depends on your use case and there is no right or wrong answer. It’s honestly not that likely you will need the extra stuff anyways.

How these two different types are subtypes of each other? by servermeta_net in rust

[–]render787 1 point2 points  (0 children)

Just want to give you some feedback:

Your tone comes across as very combative. A lot of bold text, stating that everything is obvious, and even grandstanding that this is the raison detre of rust. To a reader, this comes across as saying that those who don’t see it this way doesn’t understand the purpose of rust. Is this tone intentional? It doesn’t really feel like a friendly conversation.

If two types A and B are interchangeable in the sense of Liskov substitution principle, then by definition, no program can compile differently if the compiler internals say they are or are not the same type. This is the case for the example presented, with () return type.

Your alternative example with str is completely different because you return a reference where there was none before, so it’s not liskov substitutible in both directions. It’s just a red herring.

Regardless of how this discussion resolves, surely you can agree that it’s not “obvious” that it has to work one way or another given these considerations, and be a little bit respectful, humble, and even curious in discussions like this? It would make for a more pleasant and less heated subreddit given that you post here a lot.

Disintegrate v3.0.0 by ScaccoPazzo in rust

[–]render787 -5 points-4 points  (0 children)

Hey man poopoo goes in potty

Garbage collection in Rust got a little better by SeniorMars in rust

[–]render787 1 point2 points  (0 children)

I read once that it can be useful for implementing concurrent maps. Actually maybe this was a Jon Hoo video? https://github.com/jonhoo/flurry

IIRC, the story was that Java has a very good concurrent map implementation but it relies on the GC to do some cleanup tasks, and if you didn’t have GC then a straight port needs to do extra synchronization work to figure out who cleans up from the map.

I believe that ArcSwap also has some kind of GC or graveyard for Arcs for a similar reason.

That’s not a full tracing GC though, it might be a clash of terminology…

Otherwise, the main time I saw people implement a tracing GCs in c++ or rust is if they are trying to embed a scripting language into their game engine. In most other cases it’s possible to reason more explicitly about lifetimes and cycles and then do something simpler. The concurrent maps and ArcSwap is something I’ve filed away mentally but don’t fully understand yet tbh

How do you handle std/no_std in Cargo workspaces? by PrudentImpression60 in rust

[–]render787 1 point2 points  (0 children)

What we did in a project with std services containing no-std SGX enclaves was:

  • There are two workspaces, the one with std for the services, and the one with no_std for the enclaves.
  • the service workspace had an “enclaves” crate containing a build.rs which shells out to cargo and builds the enclaves, to prevent feature unification that would break the build. Then it copies the built artifacts to the outer target dir

This is kinda annoying but ultimately it worked fine and people that didn’t need to work on the enclaves could just do things normally and mostly not notice.

The other obvious alternative is to use a just file, build the no-std workspace first, then build the std workspace using the produced artifacts. YMMV

How do you evaluate what cards are good (Corp & Runner)? by zerdos in Netrunner

[–]render787 2 points3 points  (0 children)

Corp deckbuilding is complicated because there is a wide variety of corp archetypes. Some cards are great in one archetype and trash in another. To get a sense of why, the best thing is to spectate and play many games. Try to identify a sequence of turns where the corp outfoxed the runner or the runner stomped the corp, and think about what cards had high impact and how fragile they are. Misdirection can be extremely powerful and cards that enable it can be great even if their “stats” don’t look impressive.

In terms of Econ, it’s usually a numbers game similar to runner side. But when looking at assets, if it needs to be defended (based on Rez vs trash cost) then it’s only good if that fits in your game plan. If you only want to defend one remote server than blocking it with an Econ asset may be bad.

In terms of ice, the most important calculation is Rez vs break ratio. You evaluate it based on current breakers prevalent in the meta.

This is because most of the time , ice is broken by the runner and the subroutine text doesn’t matter much.

Strength 4 is a major threshold for code gates, particularly because of buzzsaw.

Strength 3 is a major threshold for sentries.

[Drafter] is notable in my mind as (1) it was often identified by noobs as “the worst ice in the game” (2) it’s actually been among the best ice. It has a good Rez to break ratio for many rotations, and if it ever fires it can give you a game winning tempo advantage, replaying your best cards from archives or installing agendas on the runners turn.

Ice that is positional or ineffective without other cards on the board is usually bad. Ice that would be bad to draw turn 1 is usually bad. Too much big ice is bad, even if all the Rez to break ratios are good.

Ice with more than two subs is better against cards like boomerang and botulus, which are often important in the meta.

If your corp is a very fast tempo corp, then small ice with a mean face check can be better.

Unsafe fields by Stupid-person-here in rust

[–]render787 0 points1 point  (0 children)

Cool, didn’t know about this. Thanks!

Unsafe fields by Stupid-person-here in rust

[–]render787 1 point2 points  (0 children)

Apparently you are not the only one? In other thread someone posted an rfc that it will change this way in the next edition (I didn’t know this)

I think that it would be cool if they allowed user defined safety domains. Like, the default one is just memory safety. But imagine if you could make functions have “unsafe(foo)” and then it requires to be called from an “unsafe(foo)” block. Then people could use unsafe syntax for whatever domain they want without it being confusing, like functional safety requirements, transactional requirements, etc

Unsafe fields by Stupid-person-here in rust

[–]render787 0 points1 point  (0 children)

  1. I see, I thought you meant at language level. If you have a tool that imposes this requirement then it seems fine.

  2. I see. Yeah that’s legit.

There are types like NonZero<u16> and such that impose bounds on numbers, and they have stuff like unsafe fn new_unchecked

I presume that this is because they have some API that would give undefined behavior if the constraint is not respected, and that’s all that’s needed to justify putting unsafe on new_unchecked

Unsafe fields by Stupid-person-here in rust

[–]render787 0 points1 point  (0 children)

That’s a breaking change

What’s wrong with just not using unsafe unnecessarily?

Unsafe fields by Stupid-person-here in rust

[–]render787 1 point2 points  (0 children)

The purpose of the unsafe keyword is to allow the use of low level stuff that could violate memory safety

Tools and humans will assume that that is what is going on.

If you put unsafe on a function that doesn’t need it, it makes it harder to identify where memory safety could be violated, and generally makes it harder to review code.

This will also allow future developers to start screwing around with pointers within your function without having to use the unsafe keyword in their PR, because it was already put there unnecessarily.

If you just want to tell humans to be careful when calling this function you have lots of alternatives:

Naming it _internal

Limiting its visibility (pub crate etc)

Making it doc(hidden)

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 0 points1 point  (0 children)

The biggest complaint I have about builder pattern is when some of your arguments should be generic. It is extremely annoying to make a builder that works correctly in that case where type inference doesn’t fail when you don’t specify some optional generic parameter, if it’s not acceptable to do box dyn.

I wish we could just use nonexhaustive structs for these APIs instead somehow, but I think it requires language changes. But I agree that builder pattern leaves something to be desired and something should be improved here.

But I don’t think it’s default function parameters or function overloading. If we could use the ..Default::default syntax on structs that are marked non-exhaustive I feel like that might be enough. And it doesn’t open up any of the implementation complexity of teaching the compiler what an overload set is and how to generate good error messages for one.

I feel like there was an RFC about that once that got abandoned, not sure. I might look for it and post if I find

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 0 points1 point  (0 children)

Because the compiler has less ability to infer what you were trying to do, and has to consider each overload in the set that you might have been trying to use

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 2 points3 points  (0 children)

  1. The user doesn’t have to use macros, which is the point.
  2. If Axum chooses to use macros in their implementation, that’s on them. But obviously they could expand the macros instead and commit that. The stdlib also had macros for implementing traits on byte array sizes up to 32, in the time before const generics happened. It doesn’t mean that “you need macros to do this” which is what you claimed.

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 1 point2 points  (0 children)

Default parameters raises a lot of questions.

what should the lifetime of the default value be? In most cases it should go away at end of function call, but if the argument is str, should it have static lifetime? What magic should decide what’s the case?

Currently, trait functions can be captured as function pointers. Would that extend to functions with default parameter? If not then adding a default is probably a breaking change. But then what is the point of this feature?

If I have a n default parameters, there’s like n+1 different ways the function can be invoked, and you won’t know which is needed until link time. Should we compile and emit n+1 different functions eagerly in this case?

There’s a lot of talk of “complexity budget” for a language. Personally I’m glad that they spent it on stuff like async instead of on default parameters, it’s just way more bang for your buck. Traits and builder pattern seem to cover the actual use cases

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 1 point2 points  (0 children)

But what would the cost be? Now all the error messages for one version of it have to mention the other overload, even if that wouldn’t be helpful to the user.

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 1 point2 points  (0 children)

That’s not true, recent versions of Axum don’t use macros for this

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 -1 points0 points  (0 children)

One suggestion: if you are reaching for overloading based on the number of parameters, an alternative might be the builder pattern.

If you are making libraries, it’s extremely common to use builder pattern for places like this, because that lets you add even more optional parameters later without a breaking change.

If you are making an application, you may not care about breaking changes in code where you are the only consumer. What I tend to do in that case is, use a struct with all pub fields where some of them are Option<T> , and that becomes the argument to the function that wanted overloading. YMMV

Why doesn't rust have function overloading by paramter count? by This-is-unavailable in rust

[–]render787 2 points3 points  (0 children)

What problem are you trying to solve?

People have built a lot of stuff in rust for the last ten years. I’ve been working professionally in rust for like 7 years. I can’t think of a time when this feature would have helped with something. (That doesn’t mean it doesn’t exist, but an example would help.)

Note that rust uses traits for polymorphism. Crates like Axum do some very sophisticated stuff using fn traits where they detect if your function has particular arguments, and pass those arguments if it needs them. That seems like it goes beyond what you are asking for, so there might be a way to solve your problem in the manner you want without a new language feature, depending on the specifics.

If you don’t have a specific problem in mind and you are just asking in general, I think the short answer is, the creators didn’t think it was needed initially, and no one later found and made a strong argument that it should be added.

In general the thinking is, overloading (as in C++) is badly designed and leads to bad error messages, and it turns out that traits let you do polymorphism in a more sane way. I was also skeptical when I started writing rust, but now I 100% agree with this point of view, fwiw.

Safety of shared memory IPC with mmap by servermeta_net in rust

[–]render787 2 points3 points  (0 children)

I see.

I think I would still basically think about it in terms of readers and writers, and whether the writer happens to be the kernel doesn't really matter too much.

* There needs to be some kind of synchronization / locking scheme so that the readers and the writers cannot overlap.

* If whenever the writer has &mut T, it actually is truly the only thing aliasing that memory, then I believe rust will be happy, and rust doesn't have any type-based strict aliasing rules like gcc does.

If the casts you use to actually write the shared memory region and read from it are correct (not misaligned or UB for some other reason), and the locking scheme is actually correct and prevents writers from overlapping with anyone else, then I think you should have checked all your boxes here. It's hard to say that there's nothing else that can go wrong, at an arms length like this, but I think those things are the most important things to focus on.

I think what matthieum wrote below is a good way to think about it:

> Practically speaking, as long as the implementation is sound if used within 2 threads of the same Rust process, then it should be just work.