Comment by SkiFire13

Comment by SkiFire13 9 hours ago

16 replies

> 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.

tialaramex 6 hours ago

In 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 6 hours 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.

JackYoustra 8 hours ago

Adding 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.

  • vor_ an hour ago

    > actually using swift enums and protocols is so painful.

    In what way? My understanding is they're widely used and encouraged.

  • frizlab 7 hours ago

    I 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 4 hours ago

      I 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.

  • nielsbot 7 hours ago

    I 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/7270...

    • Mond_ 6 hours ago

      It'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 6 hours ago

        To 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.

saghm 5 hours ago

You'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.

YmiYugy 8 hours ago

I 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 8 hours ago

    Similarly, 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 8 hours ago

You don't need Box here because Vec<T> is already a fixed size handle to heap data.

  • slaymaker1907 6 hours ago

    A 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 6 hours ago

      Of course, the language lets you decide. I think this anti feature is actually a feature.