Making Default a lang item so that function pointers can implement it

Wow, now that's bad. Thanks for pointing it out. Why isn't more widespread discussion on this? (Rhetorical question…)

I'm confused as to what exactly you think is bad? The fact that Default is now a lang item?

We already have approximately 130 lang items, and I'd say Default looks more deserving than many of them given the long history of e.g. default constructors in C++.

5 Likes

Because it's yet one more special-cased thing to deal with – until now, Default was a regular trait, and there is no good reason to make it special. The motivation of making "default" functions out of thin air is weak at best, and misguided at most.

If anything, C++ ctors are an example of how not to do constructors. I don't think Rust should follow their example.

Also, there being 130 lang items is not at all a good reason for adding a 131st one. It's at most a sign that the lang team is taking lang items way too liberally. Instead of working towards removing special-casing (e.g. Box, which is a lang-item for historical reasons rather than necessity, i.e. it's essentially pure technical debt), it seems we are moving towards making even more types and traits into lang-items, which could otherwise be (and were) perfectly regular library types and traits.

1 Like

I see. I do want to say that there is no rush by the lang team to remove them (ie, the amount of lang items is not really a concern). DerefMove has been a proposal since forever, and we're seeing lang items for things are "nonessential" as diagnostics.

1 Like

The mere existence of a lang item is not a problem. It's just a mechanism for the compiler to locate an item without hardcoding its path. It's purely an implementation detail of rustc- it doesn't give any indication of what the item is used for. Thus, it is not really a justification for or against anything on its own.

Certainly some lang items are more ad-hoc than others. But the existence of Box-like items (where ideally the type would not be "special") does not imply that all lang items are technical debt, or even bad at all.

The only real question is what the compiler does with the lang item. The proposals here do essentially the same thing as existing operator overloading traits- a simple, local desugaring from a syntax to a method invocation. That's the proposed change, and it would be the same regardless of whether it's implemented with a lang item or some other way.

13 Likes

"Special" can mean a ton of different things. I think that, like with explicit, it's important to be more detailed about what the complaint actually is.

"Has different coherence behaviour", for example, would be on the scarier side. But the use in question here is just "is implemented for some unnameable compiler-generated types that also implement other traits", which seems particularly innocuous.

4 Likes

From a purely selfish perspective, I don't like impls that are language items because they mean I have to update this function in rustdoc. I don't have any inherent philosophical objection to them, though.

2 Likes

Well conveniently the impls in question here are on voldemort types that aren't in rustdoc :upside_down_face:

3 Likes

The particular problem is the lack of maintainability, lack of composability, ad-hock-ness, and the hardships connected to them, arising once new traits are involved. Let me elaborate on that.

If what we really desire is abstraction over the compiler-generated unnameable types, then the corresponding lang-item should be a new auto-trait that is specific to these particular types and captures their semantics (e.g. TrivialFn or something like that), instead of making every other trait into lang-items if they happen to be touched (implemented, required, or in other interaction) by these compiler-generated types.

I don't think I need to point out the obvious, but the problem with special-casing and lang items is not purely philosophical (although I don't understand why philosophical and purity issues would have to be dismissed in the very context of laguage design). The problem with them is that they are not extensible and therefore do not compose, consequently they are much more cumbersome to treat when extension is desired.

Crucially, continuing with the example of non-capturing functions, if we want to impl other traits for them in the future, we'd have to reach for adding a new lang item to the compiler again, for each and every one of them. That is subject to inertia (although not as much as I was hoping for, apparently). This means that someone well-versed in the internals of rustc is required for even making the change – one can't just start experimenting with it e.g. in the form of their own, custom-defined trait. It also means that library-defined traits are completely excluded from the possible set of traits implementable by these types.

This goes straight against every principle of modern language and software design. DRY, single responsibility, open-closed, you name it. It is akin to making different one-shot functions for printing different strings, instead of just passing the string to be printed as a function argument.

The proper solution would be to formalize a concept connected to compiler-generated types as a new lang-item trait, and impl the required traits in terms of this lang-item. This would provide a central interface, a point of connection between compiler-intrinsic things and the outside world, instead of cluttering namespaces with all sorts of such "wormholes", also allowing library developers to hook into these types, as well as making it possible for std and the compiler to evolve separately.

(I do acknowledge, however, that doing this with multiple blanket impls would require specialization and/or a couple of trait-level tricks due to coherence, although I find the "clever" use of the trait system to be a lower-level and less serious issue than fundamental architectural rigidity and the lack of composition.)

5 Likes

Making the Default trait impl-able by std by way of a single FunctionPtr trait will still be totally possible even if Default is implemented by the compiler today, once it and specialization are usable. It wouldn't even require an RFC- it's a backwards-compatible change behind the stable API. If it truly is an architectural improvement, you or anyone can just make the change without any argument!

Again, the real question for both non-capturing functions and default value syntax has nothing to do with lang items, but with the actual exposed language and API surface. For the former, it seems you have no problem with the Default impl existing, merely with how rustc gets there internally.

For the latter, you've not really said anything actionable- there's no single trait that would suffice for both non-capturing functions and default value syntax, so there'd be two new lang items anyway. Further, I'm not sure how an extra trait would even help here- the compiler isn't defining impls, it's calling user-defined impls! Are you expecting people to eventually want to add more traits to customize default value syntax?

In both cases this really just feels like a code review-level concern, for one possible implementation, while in a pre-pre-RFC thread.

10 Likes

I have, please read my post more carefully.

No. The trait I'm proposing would/should allow one to impl Default in terms of it. Eg.:

trait NonCapturingFn: Sized {
    fn singleton() -> Self;
}

impl<T: NonCapturingFn> Default for T {
    fn default() -> Self {
        T::singleton()
    }
}

What do you mean? If we want to make Default a lang-item so that the compiler can implement it, then surely it is implementing Default, with the goal of user code being able to call it.

The change in question has already been implemented in a PR, that is why I am worried.

2 Likes

A post was merged into an existing topic: Pre-pre-RFC: syntactic sugar for Default::default()

The trait you propose has the same definition as Default, and would have to be a lang item. I wouldn't call that an improvement.

I suppose you imagine this trait containing more items than just singleton, which would allow it to subsume multiple lang items, to which I can only reply with complete indifference.

6 Likes

Why not? The point is exactly that it's specific to the kind of functions the compiler emits, thus it has semantics, and it avoids conflating a more general notion of default-ness with "the compiler knows how to make this specific type". IOW it prevents Default from becoming a heavy overloaded and leaky abstraction.

It's a bit like the newtype pattern. For instance, a primary key in a database might be implemented as a u64 or a String, and it might have the exact same representation as either of these types. However, adding methods only useful on DB keys to plain u64 and String and eventually all other types that might be used as DB keys would pollute these more general-purpose types with irrelevant use cases, and cause unnecessarily strong coupling.

1 Like

Time for you to read my post more carefully! :smiley:

I was talking about two use cases for a lang item Default- first the non-capturing functions use case, which your renamed Default does address, but also the default struct value syntax proposed in the thread this was split from.

Surely you don't intend for people to impl NonCapturingFn for their decidedly non-function types, just so that MyNonFunctionStruct { a, b, .. } can call it for the .. part? This other use case is what I was referring to with "the compiler isn't defining impls."

So review the PR, don't derail a pre-RFC thread about a different use case.

1 Like

It's not derailed any more. If you want to continue discussing the original proposal, go back to the original thread.

4 Likes