4 ms·
> Consider an enum that represents a tree. Since, it is a recursive type, Rust will force you to use something like Box<> for referencing a type within itself.
by SkiFire13 9mo ago
> Consider an enum that represents a tree. Since, it is a recursive type, Rust will force you to use something like Box<> for referencing a type within itself.
>
> enum TreeNode<T> {
> Leaf(T),
> Branch(Vec<Box<TreeNode<T>>>),
> }
>
> (You could also us Box<Vec<TreeNode<T>>> instead)
This is wrong, you don't need a `Box` here. The Rust compiler forces you to have a layer of indirection, but `Vec` already does that.
- JackYoustra 9mo agoAdding on, its also a bit much to say that Swift has a good level of sugar and reference an enum of all things. Swift's union type story is so poor that, instead of properly modeling state, people to this day still use masses of optional types in their views rather than an enum because actually using swift enums and protocols is so painful.
- nielsbot 9mo agoI don't think it's horrible, but I really do wish they would copy TypeScript here. Let me do this: const x: String | Int Instead of enum MyEnum { case string(String) case int(Int) } There's an existing proposal for this here: https://forums.swift.org/t/re-proposal-type-only-unions/72700 https://forums.swift.org/t/re-proposal-type-only-unions/7270...
- Mond_ 9mo agoIt's worth pointing out that the two examples that you're writing are actually strictly different, and not just "better syntax for the same thing". (This is assuming `String | Int` works as in Python, and the second example works as in Rust.) To understand the difference, `String | String` is just `String`. It's a union, not a sum type. There's no tag or identifier, so you cannot distinguish whether it's the first or the second string. If this sounds pedantic, this has pretty important ramifications, especially once generics get involved.
- ekimekim 9mo agoTo provide a concrete example, this bit me in a typescript codebase: type Option<T> = T | undefined function f<T>(value: T): Option<T> { ... } let thing: string | undefined = undefined; let result = f(thing); Now imagine the definition of Option is in some library or other file and you don't realize how it works. You are thinking of the Option as its own structure and expect f to return Option<string | undefined>. But Option<string | undefined> = string | undefined | undefined = string | undefined = Option<string>. The mistake here is in how Option is defined, but it's a footgun you need to be aware of.
- int_19h 8mo agoThe mistake here is that you assume that Option is a struct. For TypeScript, this is an idiomatic definition of something that is optional though.
- JackYoustra 9mo agoHijacking your comment because this is a common point that's made on the superiority of Swift syntax against the union syntax. At least with |, you're attempting to model the state space. You're saying "this is one of these things." You might get the exhaustiveness wrong, but you're in the right ballpark. As it's normally done right now, the Swift developer with five optional properties is modeling state as "maybe this, maybe that, maybe both, who knows, good luck." which is just worse than a bar. If you need to discriminate explicitly, add a `__kind` field!
- nielsbot 9mo agoI guess I just want to be able to do something like this in Swift: let x: String | Int switch x { case let value as String: // handle String value here case let value as Int: // handle Int value here } There's one more thing about TypeScript-style union types: string literals. I think it's great to be able to do type Options = "option_1" | "option_2" ... "option_n" And subsequently I could use let t: Options switch t { case "option_1": // handle `"option_1"` case here ... case "option_n": // handle `"option_n"` case here } I think this is more programmer friendly than requiring an out-of-line definition of a new `enum`. Sometimes you just want to write some code, you know?
- kibwen 9mo agoNote that union types are not the same thing as sum types, even if they're somewhat similar. https://viralinstruction.com/posts/uniontypes/ https://viralinstruction.com/posts/uniontypes/
- frizlab 9mo agoI don’t which Swift developers you are talking to, but let the record know I don’t and use enum quite a lot. And I don’t find it particularly painful though a bit verbose at times.
- willtemperley 9mo agoI think Swift enums are really well designed, but SwiftData doesn't really support them unfortunately and Observable has problems with reactivity and enums (works on iOS not macOS I've found). So lots of optionals might well be the better path here.
- vor_ 9mo ago> actually using swift enums and protocols is so painful. In what way? My understanding is they're widely used and encouraged.
- JackYoustra 9mo agoIf you look at most swiftui views, there will be a mass of optionals, rather than enum cases for every possible state that the view can occupy and a state machine transitioning between each state. If you actually try and write it all out (like go full-on and use TCA to model all the states and actions such that all and only valid states can be represented in the state types) the compiler is going to have a hard time, you're going to write masses of boilerplate, and just in general it's much rougher than zustand and expo.
- YmiYugy 9mo agoI don't think that is true. Testing with rust 1.92 enum Tree<T> { Lead(T), Branch(Vec<Tree<T>>), } works just fine, no superfluous Box needed.
- slavapestov 9mo agoSimilarly, the ‘indirect’ keyword can be omitted from Swift example in the blog post, for the same reason. A Swift Array stores its elements out of line, so an enum case payload can be an array type whose element contains the same enum, without any additional indirection.
- LukaD 9mo agoYou don't need Box here because Vec<T> is already a fixed size handle to heap data.
- slaymaker1907 9mo agoA vector of boxes is beneficial if you need to move objects around. If each T is 1000B or something, you really don’t want to copy or even do true moves in memory. Hell, even if you’re not moving things around explicitly, don’t forget that vectors resize themselves. If you use Box, then these resizes will be less painful with large objects.
- zamalek 9mo agoOf course, the language lets you decide. I think this anti feature is actually a feature.
- tialaramex 9mo agoIn one sense the places where the Rust is wrong don't trouble me because I already know Rust well, but in the end they do trouble me because it seems reasonable to assume the Swift is equally wrong but I don't know where and how. For example "In fact, Swift treats enums as more than just types and lets you put methods directly on it" seems to assume that "just types" don't have methods, which I guess is what you might assume coming from say, C++ but Rust isn't C++ and so of course all of its types, including not only user-defined enums, structures and indeed unions can have methods - but also the built-in primitive types all have methods too. 'F'.to_digit(16).unwrap() // is the 32-bit unsigned integer constant 15 Or maybe an even closer to the metal example: Rust's pointer provenance APIs get to provide a method on raw pointers which takes a closure to do stuff like hide boolean flag bits at the bottom of an aligned pointer. The fact that you're ultimately going to lower to an XOR on a CPU address register doesn't mean you shouldn't get method syntax, it's the 21st century.
- mh2266 9mo ago> In fact, Swift treats enums as more than just types and lets you put methods directly on it This section was fairly disappointing, that Rust requires you to put "} impl {" between the `enum` and its methods is... not really an interesting point. I think the title of the article is... basically correct, and the high-level point that they're both languages with modern type systems, sum types, pattern matching, and so on is good, but too many of the examples were weak.
- saghm 9mo agoYou're not wrong, but I think there's a subtle way people still might get confused about `Vec` versus `Box` with the way you've phrased it. I'd argue the important characteristic of `Vec` here isn't that it's indirection but specifically that it's sized at compile time. Compared to a `Box`, it does not provide meaningful indirection around types that are themselves not sized at compile time (e.g. dyn trait objects), and storing them inside a `Vec` itself will require a `Box` or something similar.