Pre-pre-RFC: syntactic sugar for `Default::default()`

Thanks for mentioning that. Here's the RFC for impl const. Although not yet approved, the trial implementation is undergoing active development. I think it deserves a mention in this RFC; if the default field values are const, it makes sense for #[derive(Default)] (or it's automatic counterpart) to impl const Default in the context of const_trait_impl.

I have perhaps written this before, but the fact that there are so many serious and hard-to-resolve problems around syntactic sugar for default fields (including several, apparently breaking changes as well as confusing conflicts with existing FRU syntax) quite clearly indicates to me that we should not push at least this part of the RFC.

I'm not sure what you mean here. In the simplest view, this can't affect any existing code because it'll only change things that actually add defaults to something. But more generally, the point of needing the .. in a braced struct literal is also so that existing callers won't use this either unless they make a code change (not just the struct making a code change), so they wouldn't be broken by this either.

(I scanned though your other posts in this thread but didn't see a mention of any breaking change concerns, at least.)

Here are a couple of examples from this thread: automatic Derive interaction with fieldless structs, semver concerns, accidental partial initialization is breaking.

Ah, thanks for elaborating. I agree that some of the things that have been considered (like the automatic derive) should not be included.

2 Likes

I think that there's a disconnect here, at least with my intention. I'm looking for the most conservative version of the RFC that is still "worth" the added language complexity. It seems like other people in the thread would also agree that this is a reasonable way of approaching almost any RFC.

Please correct me if I'm understanding you incorrectly, but it seems like you might be against any version of this RFC being pushed forward? Finding problems with the feature is precisely what the RFC process is all about, and this thread is to make sure that I had a more open and free-form conversation than can be had in a github PR. I'm surprised because contrary to what I feared when I started the thread, I haven't seen any big reason that would preclude some form of the feature from existing.

Which is why it was tentatively discussed and eventually discarded as an option, making writing a #[derive(Default)] still required and potentially linted for when appropriate.

The semver considerations for partial initialization are no different than those you would currently have for adding an enum variant or a field to a struct.

The concern raised there was that we are discussing making the feature const only first, but if we then change it to allow non-const default expressions for partial init in a way that the compiler has to decide whether the expression can be const or not, non-direct changes would be breaking changes because .. could stop being const by accident. To avoid this prefacing the keyword const has been proposed if we will ever want to support non-const expressions. I think in the RFC a decision will be made whether we will want to accept them at any point or whether prefacing const even if we never do is a reasonable compromise.


I think that pushback and critique of the alternatives up and including the very need of the feature is very important. Lets be mindful of others when bringing things up, as well as when they propose things. In some cases features have their RFC shot down, in others the feature comes out of the other side of the process in a much better state than it started. But I have seen RFCs devolve into a recursive mud-slinging mess where the same critiques that have been addressed come up over and over again, understandable due to sheer size of the threads or RFC text, and I would like us to make an effort to avoid that from happening again, and I fear we might be going in that direction (even though we are pretty far away from that state).

8 Likes

Have you considered adding a syntax for independently accessing the default field values? That would allow FRU to remain syntactic sugar for other code I could write out (versus the only language feature that can access the values).

1 Like

You mean something along the lines of the following?

struct S {
    field: i32 = 42,
}

fn main() {
    let s = S::field;
    assert_eq!(42, s);
}

There are many possible variations on syntax to refer to these init values/expressions, but no, I hadn't thought about it. There would be some subtleties around privacy (can you refer to the default value of a private field in a different module?) and possible syntax. I think the possible assoc const syntax would make sense and would definitely solidify my opinion on whether init expressions should only be const, even if we don't initially support this way of accessing the values.

2 Likes

If you double down on const, then default field values could perhaps even just be syntactic sugar for associated constants.

// This:
// struct Foo {
//    x: usize = 32,
//}

// Turns into:
struct Foo {
    x: usize,
}

impl Foo {
    #[allow(non_upper_case_globals)]
    const x: usize = 32;
}
2 Likes

Yeah, that's what I was thinking :slight_smile:

2 Likes

The namespace pollution seems potentially troublesome, here. If

struct MiniVec<T> { ptr: *mut T = dangling(), len: usize = 0, cap: usize = 0 }

adds an associated const called len, then it can't also have a len method.

One can always make such a constant manually and use it as = Self::Whatever in the definition if desired. And other code that wants to use the defaults could always get them with a derive macro, which seems like something it'd often want to do anyway to be able to mention the field it goes with.

Are there any other languages where the defaults are accessible as their own items? I don't know of any way to directly get function parameter or member variable defaults in C++ or C#, for example...

5 Likes

The collision consideration is something I thought wouldn't be necessary a problem as the prevalent style makes the existence of lowercased associated consts uncommon, but you're right that all assoc items are in the same namespace, which does make this a much bigger problem, but the collision is only for inherent assoc items, which is still a reasonable limitation, if not ideal.

The defaults are accessible in Python using the inspect module and specifically its Signature introspection. The equivalent would, however, be a form of reflection so it should be designed separately and probably not directly exposed in the namespace of the definition itself.

1 Like

Using associated consts for default values might also collide with a hypothetical future "first-class field" feature.

The use of presenting these with an existing feature (instead of a future reflection mechanism) is that we can keep orthogonality. Picture the scenarios of a 2015 edition or low MSRV crate wanting to enable 2021 users to use the shiny new .. syntax. Because the feature is all tied up in its own syntax, there's no way of bringing the feature to their users. (Slightly ignoring that const evaluation, or even associated consts might not be supported in their targeted MRSV, but 1) there's only so far back we could even support and 2) literal values will be such a common case, which wouldn't require const eval, that enabling just that would be useful.) On the other hand, a consumer of a new crate that provides const default init values that is stuck in an older edition might not be able to leverage the default values in APIs designed from scratch to leverage them.

That being said, for the former they are already able to do what they are doing today and for the later the alternative solution would fall on crate writers to make sure they have an API that can be used even in the absence of this feature (like having an fn new() or a builder).

I like this observation. I can translate it to Rust as "if you want to expose them to people, then you can make a derive macro that makes associated constants for them", which seems perfectly reasonable.

I don't think editions are a concern here. So long as there are no breaking changes here we can (and should) add it to old editions too -- and since it doesn't do anything without code changes (= in a definition or .. in a struct literal) then there shouldn't be any breakage.

For MSRV, my basic thought there is "meh". Allowing it to compile in a 2015 compiler means it can't use struct Foo { x: i32 = 4 } at all, and I'd rather just say that people need to increase their MSRV to use it than have some story like "well, if you add an associated constant called rust_67064f6a30a547ab807337561733d591_x then we'll use that if the the struct literal omits x and has ..". People can always make a feature to omit the defaults on old compilers if they want.

3 Likes

I saw stuff on struct but what about tuple?

type T = (i32, i32, i32 = 3);

let t = (1, 2, ..);

Would this work?

Tuple structs were briefly discussed. In my opinion, they have enough issues to avoid them, at least initially.

3 Likes

That wouldn't work because it's a type alias, not a new type, so there's nothing to which to attach the default.

(Well, we could say that it made its own type, but that would also mean that it'd be a different type from other tuples, which doesn't feel right. I think it's reasonable to say that one should make a new nominal type if you want customized defaults.)

As for tuple-structs, I agree with ekuber that leaving them out for now is probably best, since it's not clear how the generated constructor function should look.

4 Likes

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