Have you ever intentionally use Result<(), ()> instead of bool?

Ah, that makes sense.

It depends what you're using it for. One use of ControlFlow is for early exits in try_for_each, and there it can be reasonable to ignore the result the same way that you don't have to look at something indicating whether a loop used break. But I agree that in the "I'm doing a bunch of recursing" case it would be better as must_use. It may yet get it; I don't know.

There's a bit of conversation about it here: Make `ControlFlow` `#[must_use]` by LeSeulArtichaut · Pull Request #78202 · rust-lang/rust · GitHub

5 Likes

Yes.
I have a wrapper that takes a function and struct as arguments. The wrapper requires the function will return a Result. The types in the Result are generic with traits dependent on features. So then this wrapper can handle the running of different functions and loop / process the values in the Result<T, V> The Ok or Err. if needed. So if the a function wants to return valuable stuff in the result it can, and the wrapper can process the valuable stuff. *compare to strings if the T Or V is Display or Debug trait for example. If I write a simple function I am calling with the wrapper that just needs the logic of the wrapper (for example looping and user interaction.) and does not need to communicate an Ok or Err, I have those functions return OK(()) and the return signature is -> Result<(),()>

I tried using Result<!,!> thinking "no type" but that doesn't work. Using the plain Result<(),()> makes sense to me to mean "Just care if it is Ok, or Err", but not what kind of Ok or Err.

Well, ! can never be observed at the value level. It is impossible to instantiate, so nobody will pass it to your functions, either. A value of type Result<!, !> can't be observed, either, because neither the Ok nor the Err variant can be instantiated (since those both would contain a !).

There is no such thing as "no type" in Rust. Every value must have a type at all times. What you are actually thinking about is "no information". And that's exactly what zero-sized or "unit" types do: they have exactly one possible value, so they convey zero bits of information.

In contrast, empty/never/void types have exactly zero possible values, so they convey negative infinite bits of information (and thus would have negative infinite size in theory, but of course that can't be realized in a computer, and size_of is unsigned in Rust for practical reasons, so size_of::<!>() == 0, inconsistently with theory).

1 Like

Why -∞? Why not -1? How does one calculate this?

The number of bits is the (base-2) logarithm of the number of choices. (Every bit is two possibilities, so N independent bits can form 2^N different bit strings.)

Because, conceptually, ([u8; 100], !) should be size -∞, not size 99. Just subtracting off one byte isn't enough.

(Note that the "conceptually" is particularly important here. In rust that's still 100 bytes because of partial initialization logic.)

This is, in fact, the max-plus semiring, since (ignoring padding and discriminants and such) the size of an enum is the max of those of its fields and the size of a struct is the sum of those of its fields.

5 Likes

I don't understand a thing in that wikipedia article, lol.

Anyway, is there any theoretical type with size -1, 1/2, or +∞?

Size -1 would be 2⁻¹ = ½ a possible value, so no.
Size ½ would be 2½ ≈ 1.4 possible values, so also no.

Size +∞, though, totally makes sense: struct Foo(u8, Foo); is conceptually infinite-sized, since that's the "solution" to x ≥ sizeof(u8) ∧ x = x + sizeof(u8).


EDIT: Well, actually, it depends on the unit. In a theoretical discussion I'd expect to measure in bits, as the unit of information, at which point fractional sizes don't make sense.

But for Rust size is in bytes, then I guess size ½ would be u4, which you could certainly imagine existing. And if we had something like arithmetic coding or bitpacking as a language feature, then you could make a bit-packed type of size 1 that holds two u4s, at which point you could certainly think of that u4 as having "size ½", though certainly size_of would still say 1, just like size_of says the bool has size 1 even though conceptually you could think of it as having "size ⅛" in something like a bitset container.

3 Likes

It is possible to have a variable which contains half a bit of information, though it will require at least a bit of storage space. @H2CO3's statement that the number of bits is the log2 of the number of choices is a simplification that assumes each choice is equally likely. In particular, if the variable can take on two possibilities with 9∶1 odds, it conveys about half a bit of information.

5 Likes

Thank you for trying to clarify and making it more confusing. ?grin?

I think of the () unit type as a thing that is nothing. A "un" "it" type if you will.
I reason "I can never use never "!" for a type that is nothing, but using an "un it" makes logical sense.

I think this will lead you astray. I'd suggest thinking of it as something, but which is uninteresting.

It's a thing with only one possible value -- () -- and thus you don't have to even look at it to know its value. There's only one thing it could be.

(! is more "nothing", since it's impossible to have a value of its type. Thus its other name of "never".)

5 Likes

For me, the empty type ! is called empty because it contains no information at all.
On the other hand, the unit type () contains no non-trivial information, but it contains information about its existance. If you have an instance, you know that you have it - which is different from the empty type. In that case, having an instance is a fatal logic bug.
So no information vs no non-trivial information.
But the unit type encodes information! (but trivial one)

Just for fun, the type proposed,
struct Foo(u8, Foo)
has size -1/2:

(One of my Favorit Wikipedia articles)

From this perspectjve, it is even more reasonable to forbid this type :grinning:

My favourite one of those is actually 1 + 2 + 4 + 8 + … = -1, since in a sense that's exactly how we deal with signed binary numbers in computers: -1 == 0b111…111.

5 Likes

Oh, this I didn't know. Thank you!

Shorter link

(I also like these page names)

The p-adic sense is extendable to rationals in any given base. Just as .142857 repeating in decimal can be considered

.1 + .04 + ... + .000007 + .0000001 + .00000004 + ... = 1/7

In 10-adic, repeating (to the left from the decimal point) 142857. can be considered

7 + 50 + ... + 100000 + 7000000 + 500000000 + ... = -1/7

Et cetera; every rational is representable in this p-adic "repeats or terminates" form, similar to how they have a decimal representation -- except the repeating is on the left and there are always finite digits right of the decimal point. Additionally, there is no need for a negative sign. (The representation varies for each base p, as they also do for the fractional/"decimal" form.)

For -1 speciflcally, it's a sequence of Basek - Basek-1, i.e. 9 + 90 + 900 ... = repeating ...999. = -1 in 10-adic, similar to how .999... repeating = 1 in decimal. In the context of the rational examples above, 142857 * 7 = 999999; hence 142857 repeating from the decimal point is 1/7 in decimal (to the right) and -1/7 in 10-adic (to the left).

Note that the 1 + 2 + 4 ... example is for 2-adic and the corresponding sum in 10-adic does not converge. The 2-adic form using binary digits is repeating ...111. = -1. And the two's complement of n in binary is ...111. - n + 1 = -1 - n + 1 = -n.

(For some things, like extending from the rationals to a field with non-repeating infinite sequences, the base must be prime -- hence the p in p-adic. But a prime is not necessary to represent the rationals.)

1 Like

Good point.
So let's continue this idea.
Idea: The size of a type is not a natural number, but a 2-adic integer.

Then the type
struct T(T, u8)
has size -1 (right?), but cannot be instanciated.
What is the size of the empty type?
Does the sum and product rule hold for size_of? (Where sum is to be taken with a grain of salt, of course)
In particular, is the size of (T,T) equal to -2?
Can we construct a type with size +/- 1/2?

As fun as it is to talk about type theory. std::mem::size_of doesn't actually measure the number of possible choices, if it does, size_of::<Vec<()>>() would have been infinite.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.