5 ms·
Arrays and tuples are very different. Arrays are homogeneous structures, all entries have the same type. Tuples are heterogeneous, so entries can have different
by DasIch 6y ago
Arrays and tuples are very different. Arrays are homogeneous structures, all entries have the same type. Tuples are heterogeneous, so entries can have different types.
- no_wizard 6y agoThanks, after further thought, I misread the example code they posted, and realized this. I'm still getting used to how Rust specifies generic vs the way you would specify something as generic in TypeScript (a language I am far more familiar with as I use it every day in my job and all my other projects). That's what got me. Addendum Explanation (feel free to skip readers): When I say that, this is what I mean: In TypeScript, an accepted thing about generic is that I can have multiple declared types either via a union e.g. `TypeA | TypeB` or intersection `TypeA & TypeB` In general practice, it is assumed you can do this, and not vice versa. If this is supposed to be avoided, you typically will use a constraining Type or specify a specific interface / type instead of making it outright generic. This is not always the case with a language like Rust, it's much finer grained, so I must try and get out of the habit of thinking this way. It's the opposite, that generic typing is constrained by the structure you put them in, as I understand it. This of course, is a super general overview of what I'm getting at, and there is a lot more nuance to TypeScript as well, but my everyday practice of using the language for the least 6 years or so has proven this to be the case often. I'm finding (and I do like this for what it's worth) Rust is not like this outwardly.
- matt_kantor 6y agoI'm interested in understanding your addendum because I often find myself teaching programmers new languages by mapping concepts from languages they already know. Here's a TypeScript analogy that might help clarify things: https://www.typescriptlang.org/play?#code/C4TwDgpgBASgrgZ2AGwJYGsIEEBOOCGIAPACoA0UAclBAB7AQB2AJglI3ALYBGEOAfFAC8UHBHzMA9o2QgoJANoBdKADIoAbyjImAc2AALAFxUoAXwBQF0JFiIUGCCThgdARiJZBIsROmyoBSwla3BoeCQ0TGdXCAAmTwoAIW9RcSkZOSDkkJtw+yinFx0AZkSoJIoAYVTfDIDsiuqQgHoWqAA6LohgAGMgA https://www.typescriptlang.org/play?#code/C4TwDgpgBASgrgZ2AG... I'm not sure what you're getting at when mentioning unions & intersections. Are you comparing a type like `Foo<number | boolean>` with `Bar<number, boolean>`? Those are conceptually very different (even in TypeScript), but maybe I'm misunderstanding. The big difference is structural vs nominal typing and the fact that Rust doesn't really have subtyping (besides lifetimes). TypeScript types are defined by their structure—an object type you define can be compatible with a different object type from a separate library that never heard of you, just by nature of sharing the same properties. However Rust types are delineated by their names/identities—two separate types are not directly interchangeable even if they're defined identically.
- no_wizard 6y agoYeah they're definitely different. My point being that in TypeScript though, its generally assumed (unless a constraint type is used or an explicit type / interface) is used, you can generally get away with using a union or an intersection to specify a generic type value. So for instance, `Foo<number | boolean>` or `Bar<number, boolean>` wouldn't be out of place on say, a function's return type depending on what it does. Not that you should but you most certainly can. In some cases (like dealing with fetch results) its infinitely useful when doing foundational library work on a project / app / library. In other cases, I may want to use something like an intersection type to compose a return type or value type of two interfaces that are similar enough to be merged into one, or I have a composition function that merges data structures together etc. Fundamentally, with Rust, I have found, and again, (again, disclaimer: I'm really new at this language), that saying something is generic does not imply you can saturate type values like the examples I gave above (the real thing I was getting at). Its not common place to see this (at least, not that I've read or seen yet). Typically, there is a level of explicitness even within generics, like mentioned with the array. It has to be homogenous so A union type is a no go (at least, homogenous in my mind means of one uniform type). An intersection type might be idiomatic but I don't know if Rust even has this equivalent in that way. The way I think of it myself is that the compiler is trying to do the right thing not just for the developer but for the program, so these constraints likely tie back to memory safety and efficiency, so yes, the interchangeability aspect throws me for a loop when reading (it's the same words but a vastly different context) sometimes. Even C# was less strict in this way (but still a bit more strict than TypeScript, though you could just box and unbox the very annoying to see in practice most of the time `object` type since all types derive from the C# object type). F# was better in that you could do type aliasing which made certain things better (like generic Record types. I still believe F# is a better language overall in terms of ergonomics. I wonder if I could just leverage that instead of Rust, but I think the binaries would be much big (easily 50mb plus) for what I'm trying to achieve, which is web dev tools, ala things like SWC)
- matt_kantor 6y agoTypeScript-style untagged unions don't exist in (safe) Rust. Instead, "or" is done with enums (which are tagged/discriminated unions—https://en.wikipedia.org/wiki/Tagged_union https://en.wikipedia.org/wiki/Tagged_union). One difference is that in TypeScript `number | number` reduces to just `number`, but Rust enums don't work that way (`enum Foo { A(f64), B(f64) }` has two distinct variants). Check out these equivalent-ish programs: TypeScript: https://www.typescriptlang.org/play?#code/C4TwDgpgBA6gTgQzJOAeAKgPigXigbwCgoSoA3BAGwFcIAuKdQgX0MIDNqA7AY2AEsA9lyjBBAIUGDKEBFwAUADwbwkKVACMpMuVAA+ULtQC2GiHEwBKBlumyRRUlH7so80JEGvFAOgo1oHCCoACIjU3MQywJiJ1I4CGBqOBFff1ooAEJggAZYkmYoCEoAZ2hHOJIEpJSoNKpafKhWVg5uPiERbgB3RDAMTCUVPvUsa0YYp2rk1L8GiBY2HmES4CgEXAJyeYYARh8AJgBmZsJlrlWoDU38bYCGYDgM1jFJOzl5BEtCV+17eQ033Olx4NzutAYIW6AAsEMAIGRIqcen15DxLEA https://www.typescriptlang.org/play?#code/C4TwDgpgBA6gTgQzJO... type Wrapper<T> = { value: T } function toBoolean(x: Wrapper<boolean | number>): boolean { if (typeof x.value === "number") { return x.value !== 0 } else { return x.value } } function unwrap<T>(x: Wrapper<T>): T { return x.value } const a = { value: 1.23 } const b = { value: true } toBoolean(a) toBoolean(b) const c = { value: "whatever" } unwrap(c) Rust: https://play.rust-lang.org/?version=stable&edition=2018&gist=57df29387c57028e0e6c60a292835d9f https://play.rust-lang.org/?version=stable&edition=2018&gist... struct Wrapper<T> { value: T, } enum BooleanOrNumber { Boolean(bool), Number(f64), } fn to_bool(x: Wrapper<BooleanOrNumber>) -> bool { match x.value { BooleanOrNumber::Boolean(b) => b, BooleanOrNumber::Number(n) => n != 0.0, } } fn unwrap<T>(x: Wrapper<T>) -> T { x.value } fn main() { let a = Wrapper { value: BooleanOrNumber::Number(1.23) }; let b = Wrapper { value: BooleanOrNumber::Boolean(true) }; to_bool(a); to_bool(b); let c = Wrapper { value: "whatever" }; unwrap(c); } In Rust you also have traits to play with: https://play.rust-lang.org/?version=stable&edition=2018&gist=f888e94f4ce9d7b94d0cd3578946a12d https://play.rust-lang.org/?version=stable&edition=2018&gist... struct Wrapper<T> { value: T, } trait Boolable { fn to_bool(self) -> bool; } impl Boolable for bool { fn to_bool(self) -> bool { self } } impl Boolable for f64 { fn to_bool(self) -> bool { self != 0.0 } } fn to_bool(x: Wrapper<impl Boolable>) -> bool { x.value.to_bool() } fn unwrap<T>(x: Wrapper<T>) -> T { x.value } fn main() { let a = Wrapper { value: 1.23 }; let b = Wrapper { value: true }; to_bool(a); to_bool(b); let c = Wrapper { value: "whatever" }; unwrap(c); } I find there's a decent mental shift switching between TypeScript and Rust, despite some superficial similarities. I use both languages in different codebases and it usually takes me a bit to adjust when hopping back and forth. Both type systems have useful worldviews, but they're pretty different, and they nudge you towards different ways of structuring your code.
- Blikkentrekker 6y agoThey're not very different in that arrays can be thought of as a more specialized form of tuples that can thus coerce to slices. In theory, arrays do not need to exist, and could in theory be expressed as tuples, but that would lead to rather unpleasant syntax. `(A, A, A, A, A, A, A, A, A, A)` is certainly not as nice as `[A; 10]`; — Rust could even have chosen to make the latter syntactic sugar for the former and make them interchangeable. The crux to this, is that in Rust, unlike in many other languages, the length of an array is static and known at compile time. Many languages lack such a datatype, as it isn't strictly needed per the aforementioned reasons.
- steveklabnik 6y ago... and in some sense, tuples are also isomorphic to structs. Product types: woo!
- andrewflnr 6y agoThere's at least one language (Haxe?) that uses exponentiation syntax for array types, playing in the product type idea: (A,A,A,A) = A^4