carbon-lang
carbon-lang copied to clipboard
Who can create an instance of a class type?
Given:
class X {
var v: i32;
fn Make() -> X;
}
we want to permit Make to construct a value of type X by implicitly converting a struct literal to type X:
fn X.Make() -> X { return {.v = 5}; }
What code should be able to do this?
- If
Makeis a non-member function, should this be allowed? - If
Makeis a non-member function andvis private, should this be allowed? - If
Makeis a non-member function andXhas a base class, should this be allowed?
A class author often wants to ensure that their class maintains certain invariants. So I think at least in the case where X has either any non-public members or a base class, non-member (non-friend) functions should not be able to create X instances in this way. A simple rule would be that only members and friends can create X instances (even if all members are public and there are no invariants), but that would disallow the "simple struct with member functions" use case:
struct Pair {
var first: i32;
var second: i32;
fn Sum[me: Self]() { return me.first + me.second; }
}
// Error, only members and friends of Pair can create instances?
var p: Pair = {.first = 5, .second = 7};
My position is that constructing an object is the same as writing its fields. If you can write its fields you are responsible for maintaining its invariants.
I think constructing an object involves writing its fields, so we should require permission to do that, but that constructing an object should require additional permission beyond the permission to write all the fields, because there can be invariants that aren't captured by the fields. For example, there could be invariants about the state of the base class (that the derived class knows the base class won't violate if they are set up properly when constructing the base class) or there could be invariants involving state stored outside the object (such as, every instance of my class is registered in some global map). Classes with no (direct) fields at all can still maintain invariants.
Perhaps we could handle this by having some syntax for specifying that constructing an object is a "private" operation, where the default is that constructing an object is a private operation only if the class has private fields (or perhaps only if the values of any private fields have non-default values specified?). But I think that might be the wrong default, and that instead we should require classes to have to explicitly opt into allowing public construction.
We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.
This issue is labeled inactive because the last activity was over 90 days ago.
I think requiring an opt-in for public construction is going to feel like cumbersome ceremony. I think the common case is that the access for construction is going to match the access for fields. Perhaps we can introduce an appropriately named zero-sized type that can be added as a private field of your class in the exceptional cases where these two don't match? I feel like the existing situation is a straightforward rule that is easy to understand.
Within Carbon, I think it's probably somewhat common that we have types with no private members, and probably wouldn't want struct-to-type conversion to simply work. One example of this is SemIRLoc, which has constructors and factory methods, but only public members. The methods are constraining construction.
Another thing to consider might be what it means for inheritance, where:
- Parent and child both have public constructors.
- Parent has private members.
- Child has no private members.
In such a scenario, should child = {.base = Parent.Make(...)}; be expected to work? As an example within Carbon, SelfDeclaration (and within the same file, ConstraintDeclaration).