4 ms·
Oh! It's about Pattern Types! I want Pattern Types for an entirely selfish reason, I want to write BalancedI8 and similar types -- as user defined types, but i
by tialaramex 2y ago
Oh! It's about Pattern Types!
I want Pattern Types for an entirely selfish reason, I want to write BalancedI8 and similar types -- as user defined types, but in stable Rust and have them Just Work™.
BalancedI8 is similar to i8 (an 8-bit signed integer) and to NonZeroI8 (the same type but, with a niche where zero should be), instead of removing zero, which we often have a use for, lets remove the annoying most negative value i8::MIN
Why "BalancedI8"? Because now that we've removed this most negative number (to provide a niche) the remaining value is symmetrical, it goes from -127 to +127
Now, I profess that I want these types but I clearly don't want them enough to do very much about it, so that's not great.
- Yoric 2y agoHaving pattern types would be great!
- auggierose 2y agoWhat are pattern types? Cannot watch the video, too slow. Edit: Thanks for the explanations. From what I see, pattern types are subtypes of a given type, such that the subtype predicate can be specified as a pattern match. This way, pattern types are subtypes that can be checked at compile time (at least, if the used pattern can be checked at compile time, I don't know if all Rust patterns are compile-time checked).
- amelius 2y agoThe term "pattern type" is more or less invented by Rust. See this old comment, and the first reply in particular: https://news.ycombinator.com/item?id=39570633 https://news.ycombinator.com/item?id=39570633
- kreco 2y agoAnd from what I get it's basically the same idea as Ada's "subtyping"[0]. subtype Rainbow is Color range Red .. Blue; subtype Small_Int is Integer range -10 .. 10; [0] https://www.adaic.org/resources/add_content/standards/05rm/html/RM-3-2-2.html https://www.adaic.org/resources/add_content/standards/05rm/h...
- hackyhacky 2y agoWe could do that in Turbo Pascal, 35 years ago. To be honest, it wasn't particularly useful, because it requires runtime checks which panic if they fail. What is their practical use in the modern age?
- ajdude 2y agoWith Ada/SPARK (and hopefully Rust) this can be checked at compile time to formally prove code. You can also catch the runtime exceptions and handle them accordingly if you're not using SPARK. The presentation right before the Rust one[1] actually did a bit of a dive into this. I recently created a UUID library in Ada, and I'm able to validate an input string on the datatype level without ever having to worry about doing so in the function itself: subtype UUID_String is String (1 .. 36) with Dynamic_Predicate => (for all I in UUID_String'First .. UUID_String'Last => (case I is when 9 | 14 | 19 | 24 => (UUID_String (I) = '-'), when others => (UUID_String (I) in '0' .. '9' | 'A' .. 'F' | 'a' .. 'f'))); Now my function simply needs to be: function From_String (From : UUID_String) return UUID with Pre => From in UUID_String; And I can code the function with the confidence that it won't be processing a malformed string. [1] I don't think the video is ready yet, but you can view the slides here: https://fosdem.org/2025/events/attachments/fosdem-2025-4879-understanding-liquid-types-contracts-and-formal-verification-with-ada-spark/slides/237311/liquid-ty_dasDD3B.pdf https://fosdem.org/2025/events/attachments/fosdem-2025-4879-...
- tialaramex 2y agoIn Rust the bit patterns which are not occupied by a value are free for some other use, Rust calls this a "niche" and uses it extensively in its sum types. This lets us have all the performance advantages of using magic sentinel values occupying those bit pattern, but with the same ergonomics as for an ordinary sum type. For example in C a Unix file descriptor is just an integer. 0, 100, 1000 - all perfectly reasonable file descriptors. But, -1 is not a valid file descriptor, so Rust's OwnedFd is internally just an ordinary C-style integer, except, it's never -1 as a result Rust's Option<OwnedFd> is the same size as the C integer, you'll get the same machine code as the C integer, but in C you need to remember to check it's not -1 before using it, in Rust you won't make that mistake because that's not Some(fd) that's None. Rust does this with its references, Option<&T> is the same size as &T, depending on what exactly T is that's probably "really" a machine address in a CPU register, and so None is the same CPU register with an all-zeroes bit representation. My favourite non-standard library use of this feature is CompactString. CompactString is the SSO (Small String Optimisation) made famous in C++ but applied to Rust's strings. Rust's native String type is as simple as possible, thus no SSO, it's actually internally Vec<u8> plus rules to ensure it is always UTF-8 encoded text. SSO in C++ standard libraries means that "Dog" or "Cheese" are stored inline in the type itself, no need for a heap allocation. CompactString takes that to an extreme. While a typical C++ std::string might allow you to store "ycombinator.com" inside the 32 byte data structure, CompactString fits "https://ycombinator.com/ https://ycombinator.com/" in its just 24 bytes! It does this by being able to distinguish whether that last byte is a valid final UTF-8 code unit, if it is then this is a 24 byte string, but if it's not then it signals how long the rest of the string is and how the other 23 bytes should be interpreted.
- Ygg2 2y agoThey're called pattern types because the intention is to allow them to match a Rust pattern expression (more or less, no idea how conditionals would work).
- Lucretia9 2y agoPattern matching is in all functional languages, afaik.
- mbStavola 2y agoAs an update to my previous comment in the linked thread, the aforementioned minimal version of pattern types have been merged and generic support is currently being implemented[0]. Slowly but surely, we're getting there! [0]: https://github.com/rust-lang/rust/issues/136574 https://github.com/rust-lang/rust/issues/136574
- the8472 2y agohttps://gist.github.com/joboet/0cecbce925ee2ad1ee3e5520cec81e30 https://gist.github.com/joboet/0cecbce925ee2ad1ee3e5520cec81...
- Ygg2 2y agoThere is a pdf of presentation in the link... Pattern types or liquid types or dependent types are a way to express a subset of a type. E.g. type NonNull = usize is 1.. type PokerNonFaceCard = u8 is 2..11
- sampo 2y ago> I want to write BalancedI8 So, you want an integer, but one value sacrificed to denote not-a-number? Is this what "niche" means? So that with these, you can have Option[integer8, None], an option type that either contains a -127 to +127 8bit integer or contains the empty value, and still consumes only 8 bits of memory? Or are there other uses for this? And Rust already has this, and with the memory optimization, but for some reason only the version where they sacrificed the value 0. https://doc.rust-lang.org/std/num/type.NonZeroI8.html https://doc.rust-lang.org/std/num/type.NonZeroI8.html
- Joker_vD 2y agoThe main use is not having to deal with INT_MIN, the two chief problems of which is that you a) you can't sensibly abs() it, b) most people tend to forget about point a). This problem in my experiences most commonly arises in naive implementations of itoa/etc. which tend to assume that they can just call abs() and concentrate on the positive half-range signed integers. Nope, you can't do this, not all negative integers have a positive counterpart.
- deleted 2y ago[deleted]
- tialaramex 2y agoYes, I want to be able to make such types myself. You're correct that Rust provides NonZeroI8, which in fact I called out in my text. If you look at the source, you'll see that the Rust standard library is allowed to make this type but you can't in stable Rust, [rustc_layout_scalar_valid_range_start(1)] means, "Hey, Rust compiler, I promise it's fine to just assume I never have this value: 0". You absolutely could write BalancedI8.... if you're the Rust standard library. Unfortunately you aren't allowed to do that in your own stable Rust software because it needs these compiler internals. There were discussions about stabilising this idea, and they were knocked back because if you do that you're basically stuck with it forever. One day in the future NonZeroI8 could "just" be a Pattern Type, like the ones you can make at home, but today there are no Pattern Types and NonZeroI8 needs compiler magic. There are other Rust core types which you could make yourself, IPv4Addr for example is just a 32-bit value, you could make your own, but yours wouldn't be on everybody's Rust system. Option<&T> is just a sum type over &T and None, you genuinely could make your own, call it Maybe<&T> and it would work exactly the same - same optimisations even, although not all the compiler diagnostic messages would be as good if you use it wrong I think. But today NonZeroI8 isn't like them, it actually needs a sprinkle of compiler magic.
- noneeeed 2y agoNice! My first job was mostly SPARK Ada, subtypes were so useful, both in terms of making contracts clearer for the human and for the various analysis tools. Rust is high on my list of languages to learn when I can make some time, having something like this available will be great.
- eslaught 2y agoMaybe not fully ergonomic yet, but this exists today (at least for max): https://docs.rs/nonmax/latest/nonmax/ https://docs.rs/nonmax/latest/nonmax/ If you're really attached to it being min you'd have to copy that library. Edit to add: we actually use these in one of my main Rust codes; they're useful, but I'm not sure they're so useful I'd want them built into the language.
- tialaramex 2y agoYeah, the XOR trick†. It's very affordable on modern hardware (your CPU can cheerfully XOR two registers while doing other things, barely measurable) but it's more the principle of the thing. Maybe I should just put up with the XOR trick and stop thinking about Pattern Types but I don't think I'll be able to. † Just in case you've never looked inside those types, they have a constant value and when you want a NonFoo you just take a NonZero and you XOR it with that constant, in other respects they work the same way as any other wrapper type. This is one reason the NonZero types exist, they make the XOR trick very easy to do.
- lr1970 2y ago> Why "BalancedI8"? Because now that we've removed this most negative number (to provide a niche) the remaining value is symmetrical, it goes from -127 to +127 Actually instead of just removing the most negative number i8::MIN you can appropriate its bit patter to denote "missing value" (or "null" if you wish) so that your new data type is symmetric and nicely "nullable".
- tialaramex 2y agoI can't tell whether you don't know what a Rust niche is, or you do but you thought we didn't In case you didn't know yes, that's how Rust's niches work and so that's what we're talking about in this sub-thread.