Pre-RFC: User-provided default field values

I proposed the rule

below, so if that rule is accepted, the struct declaration you propose would be invalid. Does that seem alright?

At least to me the text would be easier to reason about if it didn't have

part. Simple "if"-s would make definition more straightforward I feel.

Rant

Re

pub struct Foo {
  foo: i32 = 0,
  pub bar: i32 = 0,
  ..
}

there seems to be a consensus this is to be forbidden in the initial RFC least another crate can create this struct. But why? The only reason given is to make the RFC easier to pass. Is there any knowledge that this would preclude RFC from passing? Is there anybody really decided to oppose?..

raises a hand

I don't think .. should be able to elide fields you couldn't write an initializer for. I don't think that should be supported at all, either initially or in the future. The last time the topic came up, there were others who felt the same way.

That's orthogonal to the general point that it's easier to get an RFC through if it doesn't try to do as many things at once, if it's at all possible to separate them.

4 Likes

If nothing else, it's a bit easier to specify this way, and a bit easier to implement. To name one thing that needs to be discussed otherwise: I suggested the rule

To make sure that the principle "if a struct contains .. in its declaration, then it can be constructed using construtor+.. syntax, and (using constructors) it can be constructed only in that way" is satisfied. (In particular the first part.)

If you allow private defaulted structs, you could start discussing things like e.g. a pub(crate) non-= value-defaulted field in such a struct and so on.. so one possible "rule of thumb" to end up with in this case is "everybody can construct a struct containing .. in its declaration, if they can see the most-private non-= value-defaulted field" (because non-defaulted fields need to be explicitly initialized). This is different from the simple "if it contains .., then you can construct it" situation.

Among other things, this has e.g. also implications on how the struct should be documented. (I wouldn't want to see any .. in the declaration being rendered, if the struct has non-visible (and henve non-documented) non-defaulted fields that prevent me from constructing it anyways.) Then there's the question of linting. If (in a struct with some not-public fields) the most-private non-= value-defaulted field is also the most-private field overall, then the .. has no effect (and should thus probably warn, or perhaps even error). I had only a simplified setting in mind in my original thoughts, e.g. here:

because I only considered public and private, nothing in-between.

Can you elaborate on why?

Notably, you can pattern-match with .. for fields you can't mention just fine. So I don't see why it wouldn't be ok to initialize to the defaults for fields you can't mention. (If you want to prevent Foo { .. } you can just not give the fields defaults, since the number of private construction locations are generally very few.)

But then, I think I'd be fine with allowing Vec { .. } as a struct literal, since all the fields have reasonable const defaults`. We might decide not to allow it, but I don't think it's fundamentally horrible or anything.

3 Likes

Note that (at least under what I proposed above), you would even have to explicitly opt into allowing .. for initializing non-visible fields; the opt in would be by adding the .. to the struct definition. Otherwise nobody could initialize your private fields, even if they have defaults.

somehow I was assuming exactly this

:100:

Update:

I'd answer "yes"

That makes sense outside its crate. What about inside its crate? Should it be allowed without .. in the same crate that defines it, so that that crate can do "refactor by compiler errors" even if there are defaults?

I suppose that's another question that needs to be discussed, if the support for private fields becomes part of this RFC.

Edit: Or is this a general question that applies to this feature without support for private fields, too?

Edit2: @scottmcm Sorry for so many edits. Reading this again, I think this question applies generally, since my statement was intended to apply generally, too. You're talking about the "only in that way" part, right? In my mind, this does indeed only apply to external crates. Inside of the same crate, you can skip the .. (obviously only if all fields are visible and explicitly initialized), precisely because "refactor by compiler errors" should be supported.

Along the same line of thought, I did also specify

Which explicitly only restricts construcion of structs/enums that are external to the current crate.

2 Likes

Yes, "construct" in this context is struct expressions, not method calls.

The reason for explicitly stating this is similar to how #[non_exhaustive] requires a fallback when pattern matching in external crates but not intra-crate. External crates always need to use .. in struct expressions, whereas internal usage can use it but doesn't have to.

That's fair. I'll think about the pros and cons of this.

You know, I was going to ask about why, but then I realized that trying to use a tuple struct or variant would lead to a syntactic ambiguity in that .. is a range expression. So on this point I agree.

:+1: assuming I come around to #[non_exhaustive] being a hard error

Correct.

"If and only if" has a specific meaning:

No need to discuss this point too deeply, because you also already gave a ":+1:" reaction to my own alternative formulation anyways, but to explain my thoughts, I read

as to mean, in particular

  • for a local struct, a user may not construct a value using .., if no .. is present in the struct's definition

which in my mind, formulated like this, rules out

pub struct Foo {
    pub a: i32 = 0,
}
pub fn bar() -> Foo {
    Foo { .. } // <- here
}

since around "// <- here" is a struct ,constructed using .., but the local struct definition doesn't feature "..".

I would find this restriction confusing, but perhaps I misunderstood something.

1 Like

Certainly.

I still feel the RFC will be easier to scrutinise/implement/learn if it doesn't use biconditionals in that paragraph.

The way it's been formulated is giving me a vague feeling something is not right. But I cannot put a finger on it. If re-formulated to simple "if"-s it will be easier to work with I think.

Specs don't just need to be correct, they also need to be human-friendly :slight_smile:

From the previous thread:

(More for amusement than a serious proposal for v1 of this. I agree sticking to braced-structs for now makes sense.)

My point is that changing it to just "if" alters the meaning in a significant way. But this really is an aside — I'm trying to figure out the specifics of what should and should not be permissible such that I can properly formulate it.

Oh boy…

Of course. But you can re-write it to two separate "if" sentences. I feel it will likely improve everybody's understanding.

I think this is a good point. It would be ok, in my mind, to have

struct Vec3f {
    x: f32 = 0.0,
    y: f32 = 0.0,
    z: f32 = 0.0,
}
const Y_HAT: Vec3f = Vec3f { y = 1.0, .. };

Where .. is allowed as the marker for "there are more fields that get the defaults", as distinct from "I want the compiler error for not specifying all the fields".

But this suggests that "there are defaults" is separable from "I want freedom to add more fields in future".

1 Like

For the record, I had the same feeling initially, but figured out it wasn't really the "if and only if" that was confusing me, but mostly the actual contents of what's being said. (E.g. compare my last answer above.) Do you feel the "if and only if"s in my proposed alternative, i.e.

is confusing you, too?

Actually, I'm just noticing, that the second one of those lacks the "only if" part, so perhaps "must" or "has to" together with "if and only if" did make me (uncouciously) feel uncomfortable after all, and I dropped it.

Note that "if and only if" or "if" is, in practice, often used interchangeably in definitions in maths, as far as I'm aware. (Interchangeable only in definitions, when it's clear from context that it actually means to be a precise definition, and definitely not in theorems!)


I don't feel like that would add any clarity. In any case, I agree that this is not quite relevant when the point is to figure out the actual rules, not the best way to present them.

I'm not sure I understand your point here. In my mind, the code you propose should obviously be allowed, because all the fields are visible at the point of the const definition. The relevant rule is

Using .. in a struct expression always means "fill the remaining fields (if any) with default values". Which implies that if the struct gets any additional fields with default values, then the expression will keep working, so logically, not including the ".." means "I want a compiler error if I didn't specify any of the non-defaulted fields".


Okay, a few minutes later I think I do understand your point. Yes, defaults are useful even when you don't want to opt into "freedom to add more fields in future". You might as-well only specify default because you want the custom derive(Default) implementation. That's the main reason why I start preferring an opt-in approach (to allowing .. for private fields with defaults) in the first place:

(note the part in parentheses in the second bullet point)

Feel free to confirm whether this is in line with what you meant.

1 Like

Exactly. If @jhpratt did rewrite as I asked he'd get

..which clearly doesn't make sense. I was possibly trying to politely hint :slight_smile:

Actually yes :slight_smile:

Which one is it?..

  • ... and ((not...) or ...)
  • ... and (not( ... or ...))
  • ... (and (not ...)) or ...

I think the cure is still the same. Rewrite in simpler English. Break into small sentences. Will improve everybody's understanding (including yours :-D). You may point out that there's a comma there. Right. But for me - as a non-native English speaker - that unfortunately doesn't help..

Right. These are not a definitions though. I do not say "never" use it, your 3rd bullet point above is fine. But I'm saying - if a sentence is getting as long as your 1st bullet point, please do break it down.

With logical reasoning, "must" is a particularly bad English word!1 Let's use "has to" instead.

"if user has to ... then [CONDITION]" may sound weird, but the equivalent (contrapositive) can sound a lot more meaningful:

"if not [CONDITION] then user does not have to ..."

1If I used "must", then we would arrive at "does not must", or perhaps "must not", the latter means something entirely different though, and is not the proper negation of "must".