[Pre-RFC] Inferred struct initializer (2)

Summary

Support for a language with! literal that allows initializing an inferred struct.

Motivation

It is a common practice to use struct initializers in the form Struct {} for options containers.

This prevents importing or aliasing types, as well as omits the name of common options types, context types, and node types.

use my_library::LargeOptions;

function(LargeOptions { ..default() });
function(with! { .. });

Guide-level explanation

The with! literal is used for initializing a struct without specifying its path. It supports the same contents as with struct initializers. It additionally supports an empty trailing .. component, equivalent to ..Default::default().

struct S { x: f64, y: f64 }

let object: S = with! { x: 10.0, y: 10.0 };
let object: S = with! {}; // ERROR: missing fields

Reference-level explanation

The with! literal is implemented as a native language macro whose contents resemble the braced contents of a struct initializer.

  • The with! expression requires a type annotation.
  • If an empty .. component appears, the inferred struct must implement Default, and the initializer uses Default::default() as a base object.

Drawbacks

The syntax may or may not be familiar to everyone.

Rationale and alternatives

This design is a bit more verbose than other proposals (such as _ {}), but always retains conciseness as it uses existing Rust syntax and introduces solely a native macro.

It is currently possible to implement a similiar macro, but always requiring a base object as there is no way to know the inferred struct's path. As such, this proposal allows initializing structs without a .. component.

Prior art

Languages such as JavaScript and VBScript support a legacy with statement used for a similiar purpose, but historically used for chaining operations on an existing object.

With object
    .X = 10
    .Y = 10

Unresolved questions

N/A

Future possibilities

N/A

I do not really follow what you mean here. Could you share a short code snippet that highlights the problem and how the proposed with! would fix it?

1 Like

But that's not quite the case, is it?

Consider:

struct S { x: f64, y: f64 }

let object = with! { x: 10.0, y: 10.0 };

Without annotating the type of object, what should this code do?

As the proposal mentions, there is another version of this idea, and that uses the _ { field0: true, field1: 42 } syntax or some variety of it. That has the added benefit of not looking like a macro invocation. The macro-invocation like syntax probably would be more trouble than it's worth because of the syntactic ambiguity it would introduce for the Rust parser, illustrated by this snippet:

with! { x: 10.0, y: 10.0 } 

From the POV of the parser, would that expression be a macro invocation or a with!-expression?

4 Likes

Can you elaborate why a macro-looking thing is the best syntax for this? Why is that better than, say, let object: S = .{ x: 10.0, y: 10.0 };?

It should fail with a type inference error, same as if you do let object = Default::default(); without constraining it.

You can actually do this today, albeit only in the ..default() form, using the macro in Pre-pre-RFC: syntactic sugar for `Default::default()` - #7 by scottmcm

2 Likes

I had already replied those questions before...

Here's an example:

use my_library::LargeOptions;

function(LargeOptions { ..default() });
function(with! { .. });

You still haven't explained what problem you're trying to solve. Why is this a better approach than _ { field: value }?

If we want to add a feature like this, there's no reason we need to confine it to a macro; we should choose the best design we can.

8 Likes

I'm sorry, I should have been more clear. I wasn't confused about the behavior.

The answer was meant as a rhetorical answer to, and refutation of, the quote that I started that post with, i.e. I meant to say that the with!-literal just moves the need to specify the type from the syntactic construct itself to the binding, rather than allowing its omission entirely.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.