Hacker News new | ask | show | jobs
by xscott 311 days ago
> There are way too many variants of the same thing: records, classes, struct records, tuples, struct tuples etc, [...]

> At the end I went with Rust because it has one way of doing such stuff.

I haven't looked at C# in 20 years, but Rust certainly has a LOT of ways to do similar things too:

    struct Foo { x: f64, y: f64 }          // Struct
    struct Foo(f64, f64)                   // Tuple Struct
    let f = (1.0, 2.0)                     // Tuple
    enum U { Foo(f64, f64) }               // Enum Tuple
    enum V { Foo { x: f64, y: f64 } }      // Enum Struct
    [ 1.0, 2.0 ]                           // Size-2 Array
    &[ 1.0, 2.0 ]                          // Slice
    vec![1.0, 2.0]                         // Vec
Then possibly wrapping those in Rc, Arc, Gc, Box, Cow, Option, Result, RefCell, RefMut, Cell, OnceCell, LazyCell, UnsafeCell, Weak, and so on... that's a multiplicative product of possibilities. And you still need raw pointers for interop with C libraries.

Anyways, I haven't used Rust in a few years now either, and I'm sure I've made some mistakes and omissions above, but I don't remember it as the poster child for Python's "There should be one-- and preferably only one --obvious way to do it".

3 comments

> struct Foo { x: f64, y: f64 } // Struct struct Foo(f64, f64) // Tuple Struct let f = (1.0, 2.0) // Tuple enum U { Foo(f64, f64) } // Enum Tuple enum V { Foo { x: f64, y: f64 } } // Enum Struct [ 1.0, 2.0 ] // Size-2 Array &[ 1.0, 2.0 ] // Slice vec![1.0, 2.0] // Vec

I don't know, I'm not too bothered by this. If you take struct tuple and struct, Yes synctacticqlly they are different but functionally they are quite similar. You have values lying in memory. There's no reason to prefer one to the other except convenience. Program behaviour will not change. This is not the the case for fsharp classes and records and structs. Same way Fsharp has two types of lists. It also has an array type and all these are different but they look the same. In rust case they all have different names but behaviour wise they are same: data stored in contiguous memory with some differences in what you can do with them. I do like fsharp as a language but it can't go full on its promises because of dotnet. One glaring one is nulls. The language itself claims to be null safe. Except if you use dotnet types like strings. Then the compiler doesn't warn you. Honestly Kotlin does this better with non nullable types and null chaining

Yeah, having the multiple "record" types in Rust doesn't bother me either. I just disagreed that the situation is clearly simpler than it is in F#.

If you think of using C# libraries from F# as comparable to using other people's crates in Rust, you're going to get exposed to other people's choices for data structures in both.

It’s not so much that you’ve made any mistakes or omissions as much as all of these things do different, similar things, but don’t do the exact same thing.

For example, an array and a tuple are both aggregate types, but arrays store multiple value of a single type, and tuples store multiple values of the same type.

Some of these do boil down to “named or anonymous” but that’s also two different things.

More power to you for defending or explaining Rust, but the context of the conversation is comparing multiple "record" types in C# as "bad" to the "one way" in Rust. It's hard to argue that Rust has a simpler story than C#.

There are a lot of almost orthogonal features one might choose for a record type:

Accessor: rec.x, rec.0, rec[0], match

Constant vs Mutable

Reference vs Value

Nominal vs Structural/Anonymous typing

Subtyping for inheritance

Subtyping for sum/union types

And more depending on the language (ownership in Rust)

> tuples store multiple values of the same type.

I'm sure this was a typo. :-)

It was a typo, thanks :)

I’m not trying to pass judgement on C#. I just don’t see a lot of these things as being that similar to each other.

Heh, I just remembered that Rust has like 6 or 7 ways to do strings too.
Rust has one string type in the language, and three more in the standard library. This boils down to the intersection of “owned vs borrowed” and “Rust native strings vs C native strings,” and you only need the C variants when doing FFI.
I'm (basically) aware of the details (String, &str, OsString, OsStr, CString, CStr, "star" c_char, and probably some others ("star" const i8, &[u8], ???), and you and I have had this conversation a while back when I had a stronger interest in Rust. I'm not sure if you're correcting me, but you're basically confirming what I said.

As for only needing them when you need them, how could it be otherwise? :-)

One thing I would say is if you're writing a normal Rust application or library and do not care about c interoperability, you could get by without being aware of anything other than the first 2 types. However in Fsharp you are forced to learn about all other ways of doing things, plus how C# does things, because it is almost impossible to do anything useful without interoperating with C# and dotnet
I think any new user would stumble on Path, OsString, and OsStr pretty quickly when working with files and directories. Rust tried really hard to avoid string related errors at runtime by not allowing strings to have invalid data. It's a defensible goal, and the cost is having multiple string types. And then they've got another doubling of types because of their ownership model.

It's not the choice I would make, but my opinion doesn't really matter. Real world strings (UTF-8, WTF-8, UCS-16, arbitrary bytes) don't follow the rules 100% of the time, so you're always going to end up dealing with that complexity at runtime in one place or another. I think you might as well have one simple string type and accept that. This is what Go chose (not that I'm a regular Go user either).

Yes, this is what I was trying to say. Saying “six or seven types” makes it sound far more complex than it actually is.