Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Who can create an instance of a class type? #803

Open
zygoloid opened this issue Sep 1, 2021 · 5 comments
Open

Who can create an instance of a class type? #803

zygoloid opened this issue Sep 1, 2021 · 5 comments
Labels
leads question A question for the leads team long term Issues expected to take over 90 days to resolve.

Comments

@zygoloid
Copy link
Contributor

zygoloid commented Sep 1, 2021

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 Make is a non-member function, should this be allowed?
  • If Make is a non-member function and v is private, should this be allowed?
  • If Make is a non-member function and X has 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};
@josh11b
Copy link
Contributor

josh11b commented Sep 1, 2021

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.

@zygoloid
Copy link
Contributor Author

zygoloid commented Sep 1, 2021

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.

@github-actions
Copy link

github-actions bot commented Dec 1, 2021

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.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Dec 1, 2021
@jonmeow jonmeow added long term Issues expected to take over 90 days to resolve. leads question A question for the leads team and removed inactive Issues and PRs which have been inactive for at least 90 days. labels Jul 16, 2024
@josh11b
Copy link
Contributor

josh11b commented Jul 17, 2024

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.

@jonmeow
Copy link
Contributor

jonmeow commented Jul 17, 2024

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team long term Issues expected to take over 90 days to resolve.
Projects
None yet
Development

No branches or pull requests

3 participants