It's to avoid syntactic ambiguity in the case of a single member struct. The same syntax is used for tuples like (i8,)
One of them is encouraging long, untyped argument lists, which is generally considered an anti-pattern.
Currently those things are implemented by tuples (f32, f32, f32, f32) or arrays [f32, 4] which is even less clear. What does that argument even mean? If I give you an API that has an argument of type {red: f32, green: f32, blue: f32, alpha: f32} I would consider this to be very clear intent. I don't consider Rgba {red: f32, green: f32, blue: f32, alpha: f32} in any way more clear.
Furthermore, I think it's much easier to pass around {red: f32, green: f32, blue: f32, alpha: f32} - different crates can use this exact format by agreement. If you have Rgba {red: f32, green: f32, blue: f32, alpha: f32} for this purpose - then where is it declared? Which crate does everyone else import this type from?
You can use irrefutable pattern matches in fn signatures, including on named structs in rust today. Rust Playground I don't see why their anonymity would make a difference.
I don't think this is what this pre-RFC is suggesting. On the motivation section, there's this example:
It is not suggesting that {foo: u8,} and struct Foo { foo: u8 } are different and non-interchangeable types. It is trying to instead meld the former into the latter type.
My sentiments are shared with @withoutboats, if simply a tuple with named fields are being introduced, then I'm fine with it. What I am strongly against is treating this new type as a substitute for structs in parameters (which I think is the true purpose of this pre-RFC).
I'm pretty sure that is not the purpose of this pre-RFC.
Anyway, I have become aware of a realization that would prevent me from using this in winapi. Currently you cannot do #[repr(C)] on anonymous tuples, so similarly you wouldn't be able to #[repr(C)] on anonymous records either, which means I can't actually use them in winapi. Looks like I'll be stuck with naming all my types
Iām in favour of this feature and think it is a step in the right direction towards a named field solution. I do think there are some nitty details to address (canonical ordering, repr attributes, parsing ambiguities, the .. syntax, etc.), mostly suggested in this thread.
I wouldnāt worry too much about defaults for now, I would like to see (separately) an RFC for default struct fields (I donāt think Default is a complete solution even for named structs).
@nrc I'd go for the simplest solutions to your nits:
canonical ordering
Alphabetical. (I originally went with arbitrary, but on further thought it's not like it poses a back-compat issue.)
repr attributes
Not initially supported.
parsing ambiguities
Use a comma, exactly like with tuples. (x) and {x} are already basically synonymous, so it makes sense to "escape" them the same way as (x,) and {x: y,}.
I donāt think any of those are as simple as that. There are a bunch of trade-offs for all of these things. At the least, some investigation needs doing.
When it says "this might be changed to", it doesn't mean that there will be a magical coercion; it means that the API author will change the API to use anonymous types.
We almost certainly want to allow the compiler to choose optimal orderings, but at the same time they must be consistent, and for C interop we will want to allow the ordering to be specified.
repr attributes
Initially supported or not, we need to ensure that the design can accommodate them somehow and that they can be added backwards compatibly (which is a big ask since if they affect the ordering this must be an implicit part of the type).
parsing ambiguities
The parser has all kinds of special rules around structs, blocks, and braces. Treatment of : is not exactly straightforward either. I'd want to be sure that we can in fact add anon structs without breaking these subtle special cases. Saying "just use a comma" is insufficient.
the .. syntax
Again, even if it is not in a first implementation, we need to think about how it will be incorporated in the future and that there is room in the design for it, plus backwards compatibility.
Here and in the case of repr attributes, we would want to know now whether we could support these aspects in order to assess the desirability of the feature as a whole.
By "canonical ordering" I assumed you were talking about things like Debug and printing types.
The internal ordering is of course unspecified, just like every other non-repr struct in Rust. It doesn't make sense to let people specify the order because that creates a distinction between {x: i32, y: i32} and {y: i32, x: i32}, but those types are identical. Any way of enforcing order is better done through writing a nominal type.
we need to ensure that the design can accommodate them somehow
Why? Nobody has needed them so far. I believe tuples aren't FFI safe either - at least the Rustonomicon says
DSTs, tuples, and tagged unions are not a concept in C and as such are never FFI safe.
Anonymous structs as proposed are also "not a concept in C", so they would equally not be FFI-safe.
Saying "just use a comma" is insufficient.
Fair enough on this one - the grammar needs to be more than just unambiguous.
we need to think about how it will be incorporated in the future
A suggestion which sidesteps most of @nrcās concerns (except parsing) would be to treat {a: i8, b: u32} as a āstruct literalā similar to āinteger literalsā whose specific type gets resolved by inference. So youād have
struct Options {
foo: String,
bar: f32 = 2.5, // default args
baz: u32 = 10,
}
fn do_something(options: Options) { /* ... */ }
do_something({foo: "oh no!".to_owned()});
let options = {foo: "hello".to_owned(), baz: 24};
/*...*/
do_something(options); // type of `options` gets resolved to `Options` and
// missing arguments filled in.
Layout, repr etc still get decided on the Options struct, without any additional syntax, but you still gain the ergonomics (in particular around not having to import additional types). @iopq suggested types can be added backwards compatibly (with unspecified layout/reprāsimilar to tuples) and can serve as the i32 fallback equivalent to a struct literal.
I missed that connection, but itās a good point, especially in the fallback to unnamed struct type case, since initializer_list is nameable after all.
It also feels a lot like initializer_list if used as a return value:
One place Iād like to note that this would be quite useful is in associated types, and another is in defining purely internal field types with less boilerplate. To abuse the classic example of shapes:
This occupies a nice point in between writing things out completely flat (RectangleClassic), using classic tuples, and factoring things out into entire types of their own. Unlike RectangleClassic, members with correlated behaviors are kept together. Unlike tuples (but like RectangleClassic), each field has a meaningful name. Unlike creating new nominal types, boilerplate is still kept to a minimum.
in addition, I feel these would provide a valuable midpoint along any eventual refactor from RectangleClassic to distinct types - while tuples would force a garden-path divergence where the developer removes names and then re-adds them, and custom types require #[derive] boilerplate for Copy, Clone, Debug, etc, these types (presuming that, like tuples, those traits are sensibly defaulted) have none of those drawbacks.
So IIRC C anonymous structs in anonymous unions (as opposed to other anonymous thing in non-standard C), would be served by this. So a #[repr(..)] is something weād want. I do like the literal idea, in that the repr should only be on a type so one would do ({ foo: x, bar: y }: #[repr(C)] { y: /* comes first*/ int32, x: ) or something to disambiguate.
I quite like @cristicbzās āstruct literalsā idea here.
Thereās a nice symmetry here:
let i = 0i32;
let p = Point { x: 0, y: 0 };
// or
let i : i32 = 0;
let p : Point = { x: 0, y: 0 };
Inference would still need to unambiguously find the type, all the #[derive] stuff goes in the usual place, but where something else made it clear, you donāt have to say what it is explicitly.
That gets the usage of the API to be exactly the same as proposed in the OPās piston example. And I donāt think that itās unreasonable for the API designer to make named structs for such things. Certainly I could imagine things like Position::Origin to pass instead of { x: 0.0, y: 0.0 }.
Default arguments would end up looking like Foo({ a: 32, .. FooArgs::default() }), I guess? Thatās not all that bad, actually, for a purely library solutionā¦
(Hmm, I wonder whether .. syntax would be sufficient to infer a type for a āstruct literalā?)