Why there is no `saturating_sub()` method on `NonZeroU[SIZE]` types

While I was writing the type of struct field to NonZeroU16, I discovered that it does not implement the saturating_sub() function although it still implements the saturating_add() function. When I looked through the docs, I further discovered that all of the NonZeroU[SIZE] types follow the same. I also googled to get some more insight but couldn't find much out there about this.

I just wanted to ask what's the rationale behind not having a saturating_sub() function while still having a saturating_add() function. I mean we can implement saturating_sub() with the saturating value 1.

Just a question out there. Thanks in advance.


Here is some discussion: Implement nonzero arithmetics for NonZero types. by iago-lito · Pull Request #82703 · rust-lang/rust · GitHub

1 Like

I read that and the only relevent part that I found was the note that said Possible natural Zero Result which I didn't get at all. If you know more about it, could you just give me the exact comment on which it was discussed.

It would be strange to saturate to 1 rather than to 0, given that u16 saturates to 0 and NonZeroU16 is closely related to u16.

Do you have an example use scenario?


Yup! I have a situation where a user can enter a search query on the terminal screen.

Since in my case, the first column of the terminal is taken to show a single character prompt and the rest of the row is left for typing. Now the cursor can move between column 2 to (length of the string entered + 1). To keep track of the cursor's position I use a variable and I wanted it's type to be aNonZeroU16 to keep track of the cursor's current position so that it guarantees that my cursor can never actually get to column 1 where the search prompt character is present.

I see, thanks.

My solution would be to keep track of the cursor position in terms of where in the input text you are, where 0 = beginning of text.

But that would require adding 1 to everywhere where I need to use this variable because the underlying library that my program uses (i.e crossterm) uses zero based indexing on the terminal.

Also not having to deal with 0 was the only reason I want to switch to NonZeroU* types.

If this is about development convenience, you can make your own[1] extension trait to provide the method[2] until the inherent methods get implemented (if ever).

  1. probably private ↩︎

  2. perhaps with a distinct name ↩︎


That has always been my last and sure option. I had actually opened this just to know more on why the devs have not implemented such an obvious thing


I think saturating_sub was just not included by the PR author because they felt it would be better to relegate to another PR and then nobody just followed up on that. So, I guess the libs team wouldn’t mind if someone submitted such a PR, although there might ensue some discussion on whether the use cases justify adding the methods etc. My 2c (just as a regular Rust user) it would seem that saturating_sub for NonZeroUint types would make sense and the semantics should be pretty uncontroversial (saturate to T::MIN, though that constant itself was only stabilized a couple versions ago, and saturating_add not that far back either, so the NonZero API just naturally seems to be still "in progress").


Yeah! That really cleared up a lot of things. Maybe I should hit the libs team on their opinion on this. I might open up a PR if they seem interested. Thanks dude.

1 Like

Note that if the prompt were to be more than a single character, NonZero wouldn't help at all.


Nope! The prompt character takes a single column. It's hard coded into my program.

Note that I think every method on NonZeroU32 right now does exactly the same thing as the one on u32. That's not an unchangeable property of the universe, but it avoids having to decide whether NonZeroU32 is "a u32 that can't be zero" or "a number that's in [1, 2³²)".

That's why unchecked_sub hasn't been added, for example -- for things like that where the difference is subtle the direction so far as been "just use .get() and then check it after".


checked_sub.unwrap_or(Self::MIN) can be considered the same thing.[1]

  1. Excuse any typos; on mobile ↩︎

Note that NonZeroU32::checked_add does exist, since it does exactly the same thing as u32::checked_add at a value level, just with a different type.

Whereas there's no NonZeroU32::checked_sub method today, because it wouldn't be value-identical to u32::checked_sub.

1 Like

Don't you have to subtract one at some point to convert from the terminal coordinates to the text index?

It seems very natural to add 1 to convert from the text position to the terminal coordinate if the input starts at column 1. Later, if you want to make the prompt 3 characters long, you would change "+1" to "+3". I don't see why you would need to write this "+1" conversion more than once (you can always write a function).

Yes but I would count it as just one instance where I would have to get the u16 out of the NonZeroU16 and subtract a 1 from it. I don't see any other instances of this.

As I said in the discussion above

Nope! The prompt character takes a single column. It's hard coded into my program.

The point of this thought experiment isn't that you are likely to want to change your prompt, but rather to show that NonZeroU16 isn't the best match here since it only works "by accident" for one specific prompt length. It's more natural to model this with something like:

const PROMPT_LENGTH: u16 = 1;

Okay I really don't want to offend anyone here but not having a method just because its not value identical to its parent type's method is kind of a vague reason. Like the min value of such a type is always 1 while parent u* types have their min value as 0 so it's not value wise compatible directly in every aspect. Also the NonZeroU* types exist to ensure that its range is between [1, MAX], so it has fundamentally already discarded the 0 variant.

I mean why even have such a type whose half of it's parent functions aren't even available to use. Everyone can roll out such a type into their code bases if they feel the need for such a type but we still have it n the standard library so that people can use it without having to hit their heads on making a type which they only need in one function of their entire code base.

The behavior of these types and their functions are really obvious and hence not having a function whose just because it isn't value identical to it's parent type seems kinda weird to me.

I am sorry if this offended anyone but I just wanted to put my word out there in its entirety.