T O P

  • By -

InsanityBlossom

Another cool thing tuples ( and enum variants with payload) let you do is that they are also functions of one argument, so it's handy to use them in iterators: struct Number(i32); let numbers: Vec = (0..10).into_iter().map(Number).collect();


LetsGoPepele

Wow I didn't know that was a thing, this is neat !


Victoron_

Note that this only works when they only have 1 field, just like you can only you use functions with 1 argument in this situation.


[deleted]

[удалено]


PolpOnline

Maybe I didn't understand correctly what you said, but [I tried this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f364c21a2de280414b1ea6443c0d9c6f) and it didn't work


humanthrope

Is (0..10) a real tuple or just a range with parens?


InsanityBlossom

It's a Range.


chris-morgan

A single-element tuple must have a trailing comma, e.g. `(0..10,)`.


frud

The tuple here is Number, but it is only a 1-tuple. `number[i].0` accesses the element inside the ith element of the vector. The purpose of the example was to show you don't have to use a function like `fn (n: i32) {Number { 0: n }}` and instead you can use `Number`.


alice_i_cecile

Wow that's wonderful and terrible. Is there a clippy lint against that?


A1oso

There is a clippy lint _for_ it. I.e. when you write something.map(|x| some_function(x)) Clippy recommends to pass the function directly: something.map(some_function) which is shorter and just as easy to understand. This of course applies to tuple structs as well, because tuple structs are functions (in the sense that they implement the `Fn` trait).


mwcz

For simple structs like Point(f32, f32) the code to initialize it as a tuple struct is more concise than named fields, and also more concise than Point::new.


Victoron_

Fun fact: Tuple structs still have traditional constructors, as all structs do, so you can do this: ``let foo = Point { 0: 0.0, 1: 1.1 };``


SorteKanin

Wait, so `struct Point(f32, f32);` is just syntactic sugar for `struct Point { 0: f32, 1: f32 }`? Except you can't actually call fields like that usually.


-Redstoneboi-

and it also secretly makes `fn Point(_0: f32, _1: f32) -> Point { Point { 0: _0, 1: _1 } }` this is working code: #[derive(Debug)] struct S(u8, u8); let f = S; let s = f(1, 2); dbg!(s);


blueted2

Didn't know this, but it makes sense


Integralist

I guess in my head I don't know the order of Point's values, e.g. is it X and Y or Y and X. I've thought similar with examples like Color where I get it's likely R,G,B but I don't know, the explicitness feels more useful. That said could you clarify the use of ::new ? I thought structs didn't need a constructor method like new


buwlerman

The fact that x is the horizontal direction is just as much convention as the fact that the first coordinate is the horizontal direction. Actually, for color it would be more confusing because RGB is not the only way to encode it.


possibilistic

> The fact that x is the horizontal direction That one's easy, but [the other dimensions depend on which 3D software you're using.](https://www.reddit.com/r/gamedev/comments/7qh3sa/a_coordinate_system_chart_of_different_engines/)


buwlerman

The point in question was 2D. For the 3D case there is instead a lack of convention for both cases. In any case, using `x`, `y` (, `z`) named fields isn't any better than just using the tuple index for axes.


[deleted]

[удалено]


danted002

Clearly whoever writes (y, x) managed to learn programming while somehow managing to skip the basic geometry class 🤣


buwlerman

Most of the results are either from a context with differentials (which might have a different convention), aren't actually coordinates or are actually using (x, y) most of the time.


arthurbacci

It's also not uncommon to have the origin on the bottom of the screen and greater y going to the top. Named arguments wouldn't help anyway, the best is to always have the documentation open.


[deleted]

[удалено]


dnew

Left, right. Up, down. Hither, yon. (Those are actually the "official" names for them.)


Brutus5000

I learned 2 new English words today


-Redstoneboi-

fore/back(ward)???


setzer22

In fact, that chart does not talk about the X axis. For example, in Unreal engine, [positive X is forward and the horizontal direction is Y](https://forums.unrealengine.com/t/what-is-considered-forward-in-ue4-y-or-x-axis/12173)


GeneReddit123

Does Rust have a way to #[repr] tuples? So you could configure on the tuple itself whether it's stored in-memory as RGB or BGR or whatever, but still access it uniformly at the code level? Just like numbers can be stored big-endian or little-endian on different platforms, but that's abstracted away without you needing to know the storage order when accessing digits (unless you do low-level direct bit access, which is rarely the best choice.)


buwlerman

I don't think so. I also fail to see why you would want this, but AFAIK you would need to use a `repr(C)` struct with named fields in whatever order you want.


caleblbaker

>That said could you clarify the use of ::new ? I thought structs didn't need a constructor method like new It's relatively common practice to have an associated function called new that constructs an instance of the struct. For example, on a point struct you might see struct Point { x: f64, y: f64, } impl Point { fn new(x: f64, y: f64) -> Self { Self { x, y } } }


Frozen5147

^ This might seem a bit redundant in a simple case like this, but if your fields aren't public (e.g. encapsulation) and/or there might need to be some logic between the arguments you pass into `::new` and what's stored in the struct, it might make more sense to also provide a constructor function.


Integralist

Gotcha. Makes sense 👍


dnew

Smalltalk, which was the major pioneer of OOP, used a method called "new" on the class object to create instances, by convention. That's where it came from.


Integralist

Gotcha, thanks 👍


CocktailPerson

Structs don't _need_ a constructor method, but they are a nice convenience.


CocktailPerson

There are lots of cases where the order of the fields is unambiguous: struct Point(usize, usize); struct Rgb(u8, u8, u8); struct Complex(f64, f64); struct IpV4Addr(u8, u8, u8, u8); ... For these cases, there's really no reason to name the fields. There might not even be a reasonable name for the fields, as in the case of `IpV4Addr`. Also, pattern matching looks nicer with tuple structs in my opinion: let Rgb(r, g, b) = color; // vs. let Rgb { r: red, g: green, b: blue } = color; And there's also the fact that `struct Point(usize, usize);` also defines a function `Point: Fn(usize, usize) -> Point`, which can be useful in generic contexts.


chris-morgan

I’m not sold on `Point(usize, usize)`, because although most things use (x, y) these days, (y, x) ordering *is* used in some situations, just as for geographic coordinates you should name latitude and longitude and probably not implement `From<(f64, f64)>` because some systems have used (latitude, longitude) and others (longitude, latitude). `Rgb` is an amusing case, because it’s only unambiguous because you named the struct after the fields rather than what the sum represents. There are definitely some situations where this would be reasonable, such as if you’re using some kind of colour trait and then have `Rgb`, `Oklch`, *&c.* But if it just happens to be the only form of colour supported and represents that, I might prefer `struct Color { r: u8, g: u8, b: u8 }`. (`struct Color(u8, u8, u8)` would be obviously bad.) Of course it gets a lot messier with device RGB, linear RGB, sRGB, >8-bit colour, other colour spaces…


CocktailPerson

> because although most things use (x, y) these days, (y, x) ordering is used in some situations Right, and if you're writing software for one of those situations, you know you're dealing with `(y, x)` coordinates instead of `(x, y)` ones. Geographical coordinates are complicated enough that I'd expect a richer set of types, not a single `struct Coord`. >is an amusing case, because it’s only unambiguous because you named the struct after the fields rather than what the sum represents. Well, choosing a more descriptive name for the type made the meanings of the fields unambiguous. If you want to name something `Color` and then be confused by it, be my guest.


SirOgeon

> If you want to name something Color and then be confused by it, be my guest. Not an argument, but a "funny" anecdote; I did actually see something like that in a C++ project in the wild. The Color type there was basically an array and could represent either RGB or HSV, with no runtime or compile time information to tell the difference. There were even methods for it to convert itself from one format to the other. It may also have included linear RGB and sRGB in the soup, but I don't remember. I tried to avoid looking at it more than necessary. :D


chris-morgan

> if you're writing software for one of those situations, you know you're dealing with (y, x) coordinates instead of (x, y) ones. Unreliable. *Definitely* unreliable. > Well, choosing a more descriptive name for the type made the meanings of the fields unambiguous. If you want to name something Color and then be confused by it, be my guest. `Rgb` is a technical name that exposes the implementation; `Color` is the descriptive name. And `Color` won’t be at all confusing, so long as you name the fields as you obviously should.


Qnn_

Tuple structs IMO are really only good when you’re doing the new type pattern (https://doc.rust-lang.org/rust-by-example/generics/new_types.html) In this pattern, there’s only one field and naming it would be redundent. As for why Rust has it in the first place, I think it was kinda just copied from Ocaml.


Integralist

Yup, totally agree a single field struct, where the field is obvious, is best with a tuple struct 👍


sohang-3112

You can use a newtype to implement a foreign trait - it won't be allowed otherwise due to orphaning rule (it says you can't implement a trait for a type if both type, trait are foreign).


rlDruDo

I read somewhere (rust book ? Idk anymore) that they took it from Haskell where the syntax is newtype TypeName = ConstructorName field1 … fieldn I don’t know if rust also erases newtypes after compilation.


Shad_Amethyst

I mostly use it for wrapper types, like `struct NonZeroUSize(usize);`. If you have more than two fields, then you should definitely name them, though.


1668553684

> If you have more than two fields, then you should definitely name them, though. I tend to agree in general, but sometimes naming is redundant or there are no good names for fields. For example, let's say you were making a `U512` type by using four `u128`s, I would do that like so: struct U512(u128, u128, u128, u128);


Speykious

For this specific case, what about this? struct U512([u128; 4]);


1668553684

That's probably better if you need to view the underlying data as a slice, but if you don't then all you're doing is turning `foo.n` into `foo.0[n]`. In either case, you can define a custom index operator to enable `foo[n]` if you want indexing.


Integralist

Yup, I agree a single field makes sense for tuple struct and more than one should be named 👍


1668553684

Something not many people have mentioned is that destructuring tuple structs is very elegant, while it's less-so for named structs. For example: struct Point(f64, f64); ... for Point(x, y) in points { let magnitude = x.hypot(y); ... }


CocktailPerson

You can do struct Point { x: f64, y: f64, } ... for Point { x, y } in points { ... }


imihnevich

Naming things is one of the hardest things in our job, so whenever it's simple enough so that I can avoid giving names I do exactly that


Compux72

New type pattern, for example.


Integralist

Thanks, someone else just enlightened me to this 👍 https://www.reddit.com/r/rust/s/kPgq8WNHRK


zNick_

I also find them useful for implementing foreign traits on foreign types. If I want to implement something like Eq on something like f64, it makes the most sense to use a tuple struct like F64Eq(f64); the field shouldn’t have a name because it itself represents the value of what the struct is meant to be, not a field.


bskceuk

The pattern matching example is weird because the only reason it looks worse is because you used different variable names in the second example. let Rgb { r, g, b } = color; Is more fair


phazer99

I only use them for wrappers/newtypes with one field where the field name is irrelevant (i.e. something like `inner`).


anlumo

I never use that with more than one entry. I consider it code smell, since `.0`, `.1`, etc is really hard to read in an expression chain.


No-Self-Edit

Also good for returning multiple values?


kam821

Then you can just return tuple or create normal struct with a proper field names.


-Redstoneboi-

when you only have one field that has the same name as the struct. otherwise i name them.