Live data from Hacker News

Using enums to represent state in Rust

corrode.dev

81–89 of 89 posts

Re: Using enums to represent state in Rust

#81
post #61
post #48

Earlier quoted context omitted.

> One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Ceylon had union types, which is the only place i've seen these: https://github.com/eclipse-archived/ceylon-lang.org/blob/mas... Another thing Rust enums are missing is having each variant be a type. If you have an enum Shape with variants Circle, Rectangle, and Po…

Typescript and Dart have union types that work this way.

Also, Python with type annotations. But it's not a very fair comparison, since objects in Python/TS are all heap-allocated, introspectable and contain rutnime type information. Rust doesn't have any of that by default

Re: Using enums to represent state in Rust

#82
post #79
post #50

Earlier quoted context omitted.

> The compiler can simply avoid the tag or the union when a pure struct or a pure (c-type) enum is required, respectively. I had to try: pub enum Foo { Foo { a: i32 }, } impl Foo { pub fn new() -> Self { Foo::Foo { a: 42 } } pub fn get_a(Foo::Foo{a}: &Self) -> &i32 { a } } At opt level above zero (-C opt-level=1) the tag is elided: example::Foo::new: mov eax, 42 ret example::Foo::get_a: mov rax, rdi ret https://godbo…

That really does make structs superfluous, doesn't it? The only additional thing needed is a syntax sugar, where the variant name can be avoided if there is only one variant.

Syntactic sugar to avoid the variant where there is only one, not only where used, but also when declared: enum Foo { Foo{...} }, what to do with the second Foo. And what about FFI (foreign function interface) where you have to maintain ABI compatibility over time. Factor all of that in, and it turns out Rust already has syntax for all of that: struct Foo {}.

Re: Using enums to represent state in Rust

#83
post #75

Earlier quoted context omitted.

> Position problem can be handled beautifully by variants based on row polymorphism, such as in OCaml or PureScript. There you can access the fields not by their position but by a key, like keys in objects in JS, meaning that they don't have to be ordered at all. It's like an inverse of a struct: in a struct all fields/keys are guaranteed to exist, but in a variant only one of them exists. Due to row polymorphism the…

> Either ,A>> doesn't express our intent for a function return or parameter type if we don't care about the position of A, just whether it is an A > so we'd want all nested variations normalized to Either . Sorry, perhaps my thinking is shaped by nominal type systems rather than structural, but if the only thing we care about is whether the type is A, then how do we end up having Either , A>> in the first place? Thin…

Well, you might use a different custom named type than Either, but sum types only give you basically that meaning - you can't enforce the invariants we want. You could use other type mechanisms of your language (e.g. type classes or dependent types) to embed some form of set theoretic types and hopefully leak less of your abstraction (needing "bookkeeping" to keep the invariants) or deal with restricted forms (the type violating some of the invariants we want in some situations).

The examples above or Either could result from polymorpic functions that would return a set of types that the function is abstracting about, something like: pickRandom : S, T -> S|T. With Either you would get pickRandom a1 a2 : Either (requiring cleanup if you want the invariants I wrote about), with set theoretic types you'd get A. If you have pickRandom x y you would get nested Either's or just A|B|C respectively.

Either is a Monad and so Haskell and others allow us to hide a bunch of complexity of reducing nestings by using abstractions and custom magic syntax (do notation) built for them - but the underlying complexity of the type and necessary mental model remains. Monad transformers become a necessity because you already needed the Monad magic for the cleanup, but you also have another Monad you care much more about then Either (like IO), see e.g the answer here https://stackoverflow.com/questions/67617871/reduce-nestedne... Note that this isn't talking about nested Either's, just the nested syntax for handling them without using it as a Monad and do notation, with actual nested Either's you'd need to do more cleanup.

Re: Using enums to represent state in Rust

#84
post #83

Earlier quoted context omitted.

> Either ,A>> doesn't express our intent for a function return or parameter type if we don't care about the position of A, just whether it is an A > so we'd want all nested variations normalized to Either . Sorry, perhaps my thinking is shaped by nominal type systems rather than structural, but if the only thing we care about is whether the type is A, then how do we end up having Either , A>> in the first place? Thin…

Well, you might use a different custom named type than Either, but sum types only give you basically that meaning - you can't enforce the invariants we want. You could use other type mechanisms of your language (e.g. type classes or dependent types) to embed some form of set theoretic types and hopefully leak less of your abstraction (needing "bookkeeping" to keep the invariants) or deal with restricted forms (the ty…

> If you have pickRandom x y you would get nested Either's

If this wasn't the case, how would the information about what you got be retained? It's either positional, or by a tag/key (row-polymorphic variants), or none retained.

I don't see why would you want to use monadic API for approaching an "anonymous sum type" problem in the first place. As I said before, there are fundamentally just 2 operations you would want to use: inject and project. Maybe you could also mention assoc for re-association but I'd say if you're using it you're likely handling the problem the wrong way. So I still don't see how monad transformers play into this. They are a nice (decent, at least) trick for dealing with some situations but the problem we're talking about here isn't one of them.

Re: Using enums to represent state in Rust

#85
post #72

Earlier quoted context omitted.

This feature wouldn't make a lot of sense in most cases in Rust. Rust has full type interference inside functions, so if we're inside the function body we can allow the type to be inferred, partially or entirely. For example Vec says this is a Vec of something but we're not specifying what it's a Vec of. In the function signature, Rust deliberately doesn't have inference, you must write down the types and decltype wo…

There’s one thing I can think of that looks like an exception to the lack of interference at function boundaries: you can return impl SomeTrait rather than worrying about the exact thing you’ll return. It’s useful for iterator adapters in particular, where the types depend on the functions you call and in which order, and thus aren’t stable under small modifications to the source code. (I wouldn’t count impl trait in…

That's not inference, you're literally telling callers "the object I'm giving you might have any type but I promise it implements this Trait".

The compiler knows which concrete type it is, but you needn't and your caller isn't promised it is any particular type (but it is, they just aren't allowed to care)

This is useful because all Rust's functions are types, both lambda and ordinary functions are unique types, but we often want to say I'm going to return say a predicate - we can't name the predicate we're going to return but our caller just wants a predicate so they don't care that we couldn't spell its name.

Re: Using enums to represent state in Rust

#86
post #58

This is good, but could go further if you're pursuing type-system leverage. For example, why does a deleted user have an `activate` associated function in the first place? It will error out! That's safe in Rust and perfectly fine in terms of control flow: it cannot be forgotten, and cannot easily be handled incorrectly (unlike, say, a bool, where a single exclamation mark can mean a nasty bug). But it's not ideal. I'…

This kind of thing has been very standard in other ML family languages for decades now. Rust is a nice language, but it’s frustrating when its proponents act as if it’s the first language to have these kind of features. ML is 50 years old at this point…

Everything that was old is new again. I’m glad that features that were relegated to “esoteric” languages like ML's and lisps are becoming more mainstream. There are many lucky 10k’s! :)

Re: Using enums to represent state in Rust

#87
post #48
post #26

Enums are fantastic. But they could be better! One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Another thing that a new language designer might consider is a mechanism to control the layout. For example say I have a pair of nested enums enum A { A0(B), ... A15(B), } enum B { B0, ... B15, } The outer enum A can be…

> One thing Rust could really use are anonymous unions (A | B |C instead of E::A(A), E::B(B), E::C(C)). They are to enums what tuple types are to structs. Ceylon had union types, which is the only place i've seen these: https://github.com/eclipse-archived/ceylon-lang.org/blob/mas... Another thing Rust enums are missing is having each variant be a type. If you have an enum Shape with variants Circle, Rectangle, and Po…

I haven't been following this closely, so I looked it up and it looks like that's not going to happen for the foreseeable future unfortunately:

https://github.com/rust-lang/lang-team/issues/122

Kind of a shame, but wrapper types work well enough that I understand. It does look like if there was someone with enough resources to make it happen that they'd be receptive to it.

Re: Using enums to represent state in Rust

#88
post #58

Earlier quoted context omitted.

This kind of thing has been very standard in other ML family languages for decades now. Rust is a nice language, but it’s frustrating when its proponents act as if it’s the first language to have these kind of features. ML is 50 years old at this point…

Everything that was old is new again. I’m glad that features that were relegated to “esoteric” languages like ML's and lisps are becoming more mainstream. There are many lucky 10k’s! :)

rust mainstreaming a lot of functional concepts and patterns is awesome very much agree, just pushing back on the claim that this it’s the only language where this style is prevalent.

Re: Using enums to represent state in Rust

#89
post #54

Earlier quoted context omitted.

It sounds like Rust is in dire need of something similar to C++ decltype.

This feature wouldn't make a lot of sense in most cases in Rust. Rust has full type interference inside functions, so if we're inside the function body we can allow the type to be inferred, partially or entirely. For example Vec says this is a Vec of something but we're not specifying what it's a Vec of. In the function signature, Rust deliberately doesn't have inference, you must write down the types and decltype wo…

If doesn't make a lot of sense in most cases in C++ either (and as auto gets better it has only made sense in fewer cases since it was introduced)... I write a lot of C++ and I essentially never ever ever need or even merely use decltype; but, when it does make sense, it really truly makes sense, and here we have a place in Rust that sounds exactly like the use case in C++ where this actually comes up.
Post reply on HN