Skip to content

Classes mega-RFC - #242

Open
skberkeley wants to merge 20 commits into
masterfrom
classes-mega-rfc
Open

Classes mega-RFC#242
skberkeley wants to merge 20 commits into
masterfrom
classes-mega-rfc

Conversation

@skberkeley

@skberkeley skberkeley commented Aug 13, 2026

Copy link
Copy Markdown
Member

Rendered

Per the discussion on OSS Discord, this collates the existing PRs into a single "mega-RFC" that once merged, will serve as the source of truth for how the final feature should behave.

Future class-related features should be proposed as PRs against this branch, and once those discussions have settled, this PR will be merged.

I've taken the liberty of updating the existing RFCs slightly as I collated so that the full document is internally consistent.

The mega-RFC reflects the team's stance on what classes with just the features discussed should look like. This stance may change as we consider additional features, and PRs against this branch will update existing sections if necessary.

Future discussions on the general design of classes, constructors, and implementation inheritance should take place on this thread.

@skberkeley skberkeley self-assigned this Aug 13, 2026
@TenebrisNoctua

Copy link
Copy Markdown

Since we're moving discussions to this thread, I'd like to link some of my previous comments that I believe still holds important value for constructors:

#210 (comment)
#210 (comment)

@skberkeley

Copy link
Copy Markdown
Member Author

Since we're moving discussions to this thread, I'd like to link some of my previous comments that I believe still holds important value for constructors:

#210 (comment) #210 (comment)

To summarize, I believe the two main points of concern are:

  • Using .new() as the blessed path for constructing new class instances.
  • Allowing constructors to be inheritable.

I can't speak more on the first point beyond saying that this is a hard requirement from internal stakeholders, but maybe @andyfriesen can.

On the second, we share your concern, and have included your suggestion that child classes must define constructors if their parents do so! This is specified in Constructors - Typechecking.

@TenebrisNoctua

TenebrisNoctua commented Aug 13, 2026

Copy link
Copy Markdown

Since we're moving discussions to this thread, I'd like to link some of my previous comments that I believe still holds important value for constructors:
#210 (comment) #210 (comment)

To summarize, I believe the two main points of concern are:

* Using `.new()` as the blessed path for constructing new class instances.

* Allowing constructors to be inheritable.

I can't speak more on the first point beyond saying that this is a hard requirement from internal stakeholders, but maybe @andyfriesen can.

On the second, we share your concern, and have included your suggestion that child classes must define constructors if their parents do so! This is specified in Constructors - Typechecking.

I would really want the team to present all of these potential pain points in implementation and syntax to internal stakeholders. We have seen this play-out before, and it is understandable that the team would consider their requirements first, because, well, Roblox is the primary customer, but this requirement benefits no one.

There can be workarounds found for those who wish to easily have a method such as .new() available for them. With the current syntax, it is significantly easier to define a static new method inside the class and construct an instance of a class.

I've also proposed the new keyword as an alternative solution, it brings in similar semantics to how objects are created in C++/C#, and removes the reserving requirement.

The team could also consider implementing C++ like template classes, with generic support. C++'s template classes are quite useful, and if brought to Luau in a way that doesn't increase the syntax complexity too much, it would be an amazing alternative. The user would create a class from a template that contains .new(), and move on.

I would really suggest that the team listens the community on this one, because I believe community will use classes more than anyone else. If this requirement stays, then I believe the community backlash and confusion will be imminent.

For the second point, Andy did update the previous RFC to include an additional rule about that, so that was great.

Comment thread docs/classes-mega-rfc.md Outdated
Comment thread docs/classes-mega-rfc.md Outdated
Comment thread docs/classes-mega-rfc.md Outdated
Comment thread docs/classes-mega-rfc.md Outdated
Comment thread docs/classes-mega-rfc.md Outdated
@InfraredGodYT

Copy link
Copy Markdown

Since we're moving discussions to this thread, I'd like to link some of my previous comments that I believe still holds important value for constructors:

#210 (comment) #210 (comment)

I agree. The RFC states the motivation behind this by saying:

The choice to construct instances via a .new static function is motivated by Lua libraries that effect objects. We sacrifice the ability for classes to define their own .new method but in exchange, code using classes looks more consistent with what came before.

This is really just enforcing a status quo rather than guaranteeing consistency due to the drawbacks of making a .new method mandatory. Luau’s own native buffer and vector libraries using .create (possibly because of coroutines) also somewhat undermine the motivation for this constructor syntax since this is a Luau feature that isn’t limited to Roblox.

Comment thread docs/classes-mega-rfc.md Outdated
@funwolf7

Copy link
Copy Markdown

The main concern I have with new being the forced constructor name is that in certain cases where there is a need to enforce invariants, it almost feels like there wouldn't be a way to make a public new constructor.

Since there is only 1 constructor, when designing a class you have to make sure that constructor really counts. If there are any static factory methods, this 1 constructor must support constructing a class in any way the factory functions support constructing the class. If there are no invariants and all properties are public, this is fine, since the default constructor will work for this, but when you start needing invariants (or if private properties become a thing), this becomes problematic.

If you just make a simple constructor that allows setting the properties in any way (or use the default constructor), invariants become difficult to enforce. Perhaps you could enforce the invariants through assertions in the constructor, but the invariants may be difficult or slow to check without additional context from the factory methods, and you might not want some properties to even be settable from outside of public code. It would just quickly become a mess as you add more factory methods, each with their own rules for what is allowed.

So, the simple solution one might do is to instead just make the constructor private but powerful and with no enforcement of invariants (obviously private doesn't exist right now, but we are looking to the future). Then, invariants can be enforced in the factory methods where they have full context, and the factory methods can construct arbitrary class objects however they need. This is very similar to what Rust requires you to do.

However, because we have made the constructor private, we are now blocked from using a constructor named new in the public API. So, ironically, we are now forced away from the standard of using new as a constructor. That's my biggest issue.

Languages like C++ and Java bypass this problem by allowing you to overload a private, more powerful constructor. That isn't really an option in Luau, however.

@Kenji-Shore

Kenji-Shore commented Aug 20, 2026

Copy link
Copy Markdown

This might be too little too late since the discussion around inheritance has pretty much settled down, but is there any chance the concept of abstract classes could make its way in? The usefulness of abstract comes from being able to constrain the type of any derived classes.

abstract class Base
   abstract function method(self, x: number|string): {}?
end

class ChildOne extends Base
    function method(self, x: number|string?): {x: string}
        return {x="hello"}
    end
end

class ChildTwo extends Base
    function method(self, x: number|string?): {x: string}
        return {x="other"}
    end
end

const function operateOnClassObjectsWithMethod(object: Base)
   object:method("stuff")
end

The example function accepts a parameter object: Base which could be ChildOne or ChildTwo (but not Base itself); we know that method(self, x: number|string): {}? is guaranteed to exist in object.

abstract could be a standalone keyword that also designates the class as open, or alternatively the two keywords could be used in conjunction: abstract open


If abstract is off the table, would it instead be possible to create a type alias that is denoted to be a class type which defines field and method types, and then allow classes to extend off of said type alias (aka how classes implements interfaces in typescript).

The other aspect of abstract that is not covered by the aforementioned interface-like functionality would be that abstract classes can also contain non-abstract methods, which derived classes inherit normally. The lack of abstract communicates that all classes are supposed to be valid classes that can be initialized, which may not always be the case (such as the common use case where a superclass defines methods that are used in its derived classes, but the superclass is not functional on its own and would not make sense to be instantiated).

I know the whole inheritance thing is pretty much settled at this point, but I feel like existing discussions overlooked a lot of the key functionality that abstract provides which the current proposed classes have no answer for.

@skberkeley

Copy link
Copy Markdown
Member Author

Hello! We've had both interface inheritance and marking individual methods as open or inheritable or abstract floated in the past. To avoid taking on too much complexity, we decided that they are not necessary for this initial version of classes, but we're open to discussing them in the future!

@TenebrisNoctua

Copy link
Copy Markdown

Hello! We've had both interface inheritance and marking individual methods as open or inheritable or abstract floated in the past. To avoid taking on too much complexity, we decided that they are not necessary for this initial version of classes, but we're open to discussing them in the future!

I'd also love to see abstract classes eventually. I personally think they should be a part of the classes syntax rather than a new "interface" syntax. Would make things easier and more consistent.

Though comes the question, if we're going to use extends to implement abstract classes, then what about multi-interface inheritance? Should that be an exception?

* forbid overriding comparison metamethods.

* note that `__eq` doesn't throw

* may reserve other metamethods
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants